Web API package for use when compling Go to WASM
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

604 lines
14 KiB

package main
import (
"fmt"
"go/format"
"os"
"strings"
)
type Generator struct {
fname string
specs SpecMap
}
func NewGenerator(fname string) (*Generator, error) {
specs, err := LoadSpecs(fname)
return &Generator{
fname: fname,
specs: specs,
}, err
}
func (g *Generator) writeInterfaceMember(m Member, b *Builder) error {
if m.Title() == "" {
return nil
}
switch m.Type {
case "attribute":
rt := convertIDLType(m.IDLType)
b.WriteF("Get%s() %s", m.Title(), rt)
if !m.IsReadOnly() {
b.WriteF("Set%s(%s)", m.Title(), rt)
}
case "operation":
b.WriteF("%s(args ...interface{}) %s", m.Title(), convertIDLType(m.Body.IDLType))
}
return nil
}
func (g *Generator) writeStructMember(s Spec, m Member, b *Builder) error {
if m.Title() == "" {
return nil
}
switch m.Type {
case "attribute":
rt := convertIDLType(m.IDLType)
if rt == "" {
rt = "Value"
}
b.WriteF("func %s Get%s() %s {", s.Receiver(), m.Title(), rt)
if rt != "" {
b.WriteF("val := %s.Get(\"%s\")", s.Shortname(), m.Name)
switch rt {
case "string":
b.WriteF("return val.String()")
case "[]byte":
b.WriteF("return []byte(val.String())")
case "bool":
b.WriteF("return val.Bool()")
case "float64":
b.WriteF("return val.Float()")
case "int":
b.WriteF("return val.Int()")
case "Value":
b.WriteString("return val")
default:
b.WriteF("return JSValueTo%s(val.JSValue())", rt)
}
} else {
b.WriteF("%s.Get(\"%s\")", s.Shortname(), m.Name)
}
b.WriteString("}")
if !m.IsReadOnly() {
b.WriteF("func %s Set%s(val %s) {", s.Receiver(), m.Title(), rt)
b.WriteF("%s.Set(\"%s\", val)", s.Shortname(), m.Name)
b.WriteString("}")
}
case "operation":
rt := convertIDLType(m.Body.IDLType)
b.WriteF("func %s %s(args ...interface{}) %s {", s.Receiver(), m.Title(), rt)
if rt != "" {
b.WriteF("val := %s.Call(\"%s\", args...)", s.Shortname(), m.Body.Name.Value)
switch rt {
case "string":
b.WriteF("return val.String()")
case "[]byte":
b.WriteF("return []byte(val.String())")
case "bool":
b.WriteF("return val.Bool()")
case "float64":
b.WriteF("return val.Float()")
case "int":
b.WriteF("return val.Int()")
case "Value":
b.WriteF("return val")
default:
b.WriteF("return JSValueTo%s(val.JSValue())", rt)
}
} else {
b.WriteF("%s.Call(\"%s\", args...)", s.Shortname(), m.Body.Name.Value)
}
b.WriteString("}")
}
return nil
}
func (g *Generator) generateFileHeader(spec Spec, b *Builder, importJs bool) {
b.WriteString("// Code generated DO NOT EDIT")
b.WriteF("// %s", spec.Filename())
b.WriteString("package dom")
if importJs {
b.WriteString("import \"syscall/js\"")
}
}
func (g *Generator) writeFile(spec Spec, b *Builder) (err error) {
source, err := format.Source(b.Bytes())
if err != nil {
return err
}
out, err := os.OpenFile(spec.Filename(), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer out.Close()
_, err = out.Write(source)
return err
}
func (g *Generator) generateInterface(spec Spec) error {
b := NewBuilder()
g.generateFileHeader(spec, b, true)
// Interface
b.WriteF("type %sIFace interface {", spec.Name)
mems, err := spec.ResolveMembers(g.specs, true)
if err != nil {
return err
}
for _, m := range mems {
err := g.writeInterfaceMember(m, b)
if err != nil {
return err
}
}
b.WriteString("}")
// Implementation
b.WriteF("type %s struct {", spec.Name)
b.WriteString("Value")
inheritance, err := spec.ResolveInheritance(g.specs)
if err != nil {
return err
}
for _, i := range inheritance {
b.WriteString(i.Name)
}
b.WriteString("}")
b.WriteF("func JSValueTo%s(val js.Value) %s { return %s{ Value: JSValueToValue(val) } }", spec.Name, spec.Name, spec.Name)
b.WriteF("func (v Value) As%s() %s { return %s{Value: v} }", spec.Name, spec.Name, spec.Name)
b.WriteF("func New%s(args ...interface{}) %s { return %s{ Value: JSValueToValue(js.Global().Get(\"%s\").New(args...)) } }", spec.Name, spec.Name, spec.Name, spec.Name)
mems, err = spec.ResolveMembers(g.specs, false)
if err != nil {
return err
}
for _, m := range mems {
err := g.writeStructMember(spec, m, b)
if err != nil {
return err
}
}
return g.writeFile(spec, b)
}
func (g *Generator) generateCallbackInterface(spec Spec) error {
b := NewBuilder()
g.generateFileHeader(spec, b, true)
mems, err := spec.ResolveMembers(g.specs, true)
if err != nil {
return err
}
consts := make([]Member, 0)
funcs := make([]Member, 0)
for _, m := range mems {
switch m.Type {
case "const":
consts = append(consts, m)
case "operation":
funcs = append(funcs, m)
default:
fmt.Printf("%#v\r\n", m)
}
}
if len(consts) > 0 {
b.WriteString("const (")
for _, c := range consts {
n := fmt.Sprintf("%s%s", spec.Name, c.Title())
t := convertIDLType(c.IDLType)
b.WriteF("%s %s = %s", n, t, c.Value.Value)
}
b.WriteString(")")
}
for _, f := range funcs {
n := fmt.Sprintf("%s%s", spec.Name, f.Title())
args := ""
params := ""
for i, a := range f.Body.Arguments {
if i > 0 {
args += ","
params += ","
}
t := convertIDLType(a.IDLType)
if t == "" {
t = "Value"
}
args += fmt.Sprintf("%s %s", a.Name, t)
params += a.Name
}
b.WriteF("type %sCallback func(%s) %s", n, args, convertIDLType(f.IDLType))
b.WriteF("type %s struct {", n)
b.WriteString("Callback")
b.WriteString("}")
b.WriteF("func JSValueTo%s(val js.Value) %s {", n, n)
b.WriteF("return %s{ Callback: JSValueToCallback(val) }", n)
b.WriteString("}")
b.WriteF("func New%s(c %sCallback) %s {", n, n, n)
b.WriteF("callback := js.NewCallback(func (args []js.Value) {")
for i, a := range f.Body.Arguments {
if a.Name == "c" || a.Name == "args" {
a.Name += "Arg"
}
t := convertIDLType(a.IDLType)
if t == "" {
t = "Value"
}
switch t {
case "string":
b.WriteF("%s := args[%d].String()", a.Name, i)
case "[]byte":
b.WriteF("%s := []byte(args[%d].String())", a.Name, i)
case "bool":
b.WriteF("%s := args[%d].Bool()", a.Name, i)
case "float64":
b.WriteF("%s := args[%d].Float()", a.Name, i)
case "int":
b.WriteF("%s := args[%d].Int()", a.Name, i)
default:
b.WriteF("%s := JSValueTo%s(args[%d])", a.Name, t, i)
}
}
b.WriteF("c(%s)", params)
b.WriteString("})")
b.WriteF("return %s{ Callback: Callback{ Callback: callback } }", n)
b.WriteString("}")
}
// Implementation
b.WriteF("type %s struct {", spec.Name)
b.WriteString("Value")
for _, f := range funcs {
b.WriteF("%s %s%sCallback", f.Title(), spec.Name, f.Title())
}
b.WriteString("}")
b.WriteF("func JSValueTo%s(val js.Value) %s { return %s{ Value: Value { Value: val }}}", spec.Name, spec.Name, spec.Name)
b.WriteF("func (v Value) As%s() %s { return %s{Value: v} }", spec.Name, spec.Name, spec.Name)
return g.writeFile(spec, b)
}
func (g *Generator) generateCallback(spec Spec) (err error) {
b := NewBuilder()
g.generateFileHeader(spec, b, true)
args := ""
params := ""
for i, a := range spec.Arguments {
if i > 0 {
args += ","
params += ","
}
t := convertIDLType(a.IDLType)
if t == "" {
t = "Value"
}
args += fmt.Sprintf("%s %s", a.Name, t)
params += a.Name
}
b.WriteF("type %sCallback func(%s)", spec.Name, args)
b.WriteF("type %s struct {", spec.Name)
b.WriteString("Callback")
b.WriteString("}")
b.WriteF("func JSValueTo%s(val js.Value) %s {", spec.Name, spec.Name)
b.WriteF("return %s{ Callback: JSValueToCallback(val) }", spec.Name)
b.WriteString("}")
b.WriteF("func New%s(c %sCallback) %s {", spec.Name, spec.Name, spec.Name)
b.WriteF("callback := js.NewCallback(func (args []js.Value) {")
for i, a := range spec.Arguments {
if a.Name == "c" || a.Name == "args" {
a.Name += "Arg"
}
t := convertIDLType(a.IDLType)
if t == "" {
t = "Value"
}
switch t {
case "string":
b.WriteF("%s := args[%d].String()", a.Name, i)
case "[]byte":
b.WriteF("%s := []byte(args[%d].String())", a.Name, i)
case "bool":
b.WriteF("%s := args[%d].Bool()", a.Name, i)
case "float64":
b.WriteF("%s := args[%d].Float()", a.Name, i)
case "int":
b.WriteF("%s := args[%d].Int()", a.Name, i)
default:
b.WriteF("%s := JSValueTo%s(args[%d])", a.Name, t, i)
}
}
b.WriteF("c(%s)", params)
b.WriteString("})")
b.WriteF("return %s{ Callback: Callback{ Callback: callback } }", spec.Name)
b.WriteString("}")
return g.writeFile(spec, b)
}
func (g *Generator) generateTypedef(spec Spec) (err error) {
b := NewBuilder()
g.generateFileHeader(spec, b, true)
t := convertIDLType(spec.IDLType)
if t == "" {
t = "Value"
}
b.WriteF("type %s %s", spec.Name, t)
b.WriteF("func JSValueTo%s(val js.Value) %s {", spec.Name, spec.Name)
switch t {
case "string":
b.WriteF("return %s(val.String())", spec.Name)
case "[]byte":
b.WriteF("return %s([]byte(val.String()))", spec.Name)
case "bool":
b.WriteF("return %s(val.Bool())", spec.Name)
case "float64":
b.WriteF("return %s(val.Float())", spec.Name)
case "int":
b.WriteF("return %s(val.Int())", spec.Name)
default:
b.WriteF("return %s(JSValueTo%s(val))", spec.Name, t)
}
b.WriteString("}")
p, ok := g.specs[t]
if ok && p.Type == "callback" {
b.WriteF("func New%s(c %sCallback) %s {", spec.Name, p.Name, spec.Name)
b.WriteF("return %s(New%s(c))", spec.Name, p.Name)
b.WriteString("}")
}
return g.writeFile(spec, b)
}
func (g *Generator) generateEnum(spec Spec) (err error) {
b := NewBuilder()
g.generateFileHeader(spec, b, true)
t := convertIDLType(spec.IDLType)
if t == "" {
t = "string"
}
b.WriteF("type %s %s", spec.Name, t)
b.WriteString("const (")
for _, v := range spec.Values {
if v.Value == "" {
v.Value = "Empty"
}
n := fmt.Sprintf("%s%s", spec.Name, strings.Title(v.Value))
n = strings.Replace(n, "-", "", -1)
n = strings.Replace(n, "/", "", -1)
n = strings.Replace(n, "+", "", -1)
b.WriteF("%s %s = %q", n, spec.Name, v.Value)
}
b.WriteString(")")
b.WriteF("func JSValueTo%s(val js.Value) %s {", spec.Name, spec.Name)
switch t {
case "string":
b.WriteF("return %s(val.String())", spec.Name)
case "[]byte":
b.WriteF("return %s([]byte(val.String()))", spec.Name)
case "bool":
b.WriteF("return %s(val.Bool())", spec.Name)
case "float64":
b.WriteF("return %s(val.Float())", spec.Name)
case "int":
b.WriteF("return %s(val.Int())", spec.Name)
default:
b.WriteF("return %s(JSValueTo%s(val))", spec.Name, t)
}
b.WriteString("}")
return g.writeFile(spec, b)
}
func (g *Generator) generateNamespace(spec Spec) (err error) {
d := strings.ToLower(spec.Name)
fname := fmt.Sprintf("%s/%s.go", d, d)
err = os.MkdirAll(d, os.ModePerm)
if err != nil {
return
}
b := NewBuilder()
b.WriteString("// Code generated DO NOT EDIT")
b.WriteF("// %s", fname)
b.WriteF("package %s", d)
b.WriteString("import dom \"github.com/brettlangdon/go-dom\"")
b.WriteString("import \"syscall/js\"")
vt := "Value"
if spec.Type == "interface" {
vt = spec.Name
}
b.WriteF("var value dom.%s", vt)
b.WriteF("func init() { value = dom.JSValueTo%s(js.Global().Get(\"%s\")) }", vt, strings.ToLower(spec.Name))
mems, err := spec.ResolveMembers(g.specs, true)
if err != nil {
return
}
for _, m := range mems {
switch m.Type {
case "attribute":
t := convertIDLType(m.IDLType)
if t == "" {
t = "Value"
}
rt := t
switch rt {
case "string", "[]byte", "bool", "float64", "int":
default:
rt = "dom." + rt
}
b.WriteF("func Get%s() %s {", m.Title(), rt)
if spec.Type == "interface" {
b.WriteF("return value.Get%s()", m.Title())
} else {
if rt != "" {
b.WriteF("val := value.Get(\"%s\")", m.Name)
switch rt {
case "string":
b.WriteF("return val.String()")
case "[]byte":
b.WriteF("return []byte(val.String())")
case "bool":
b.WriteF("return val.Bool()")
case "float64":
b.WriteF("return val.Float()")
case "int":
b.WriteF("return val.Int()")
case "Value":
b.WriteString("return val")
default:
b.WriteF("return dom.JSValueTo%s(val.JSValue())", t)
}
} else {
b.WriteF("value.Get(\"%s\")", m.Name)
}
}
b.WriteString("}")
if !m.IsReadOnly() {
if spec.Type == "interface" {
b.WriteF("func Set%s(val %s) { value.Set%s(val) }", m.Title(), rt, m.Title())
} else {
b.WriteF("func Set%s(val %s) { value.Set(\"%s\", val) }", m.Title(), rt, m.Name)
}
}
case "operation":
n := m.Body.Name.Value
t := convertIDLType(m.Body.IDLType)
rt := t
switch rt {
case "string", "[]byte", "bool", "float64", "int":
case "":
default:
rt = "dom." + rt
}
b.WriteF("func %s(args ...interface{}) %s {", m.Title(), rt)
if rt != "" {
b.WriteF("val := value.Call(\"%s\", args...)", n)
switch rt {
case "string":
b.WriteString("return val.String()")
case "[]byte":
b.WriteString("return []byte(val.String())")
case "bool":
b.WriteString("return val.Bool()")
case "float64":
b.WriteString("return val.Float()")
case "int":
b.WriteString("return val.Int()")
case "dom.Value":
b.WriteString("return val")
default:
b.WriteF("return dom.JSValueTo%s(val.JSValue())", t)
}
} else {
b.WriteF("value.Call(\"%s\", args...)", n)
}
b.WriteString("}")
}
}
source, err := format.Source(b.Bytes())
if err != nil {
return
}
out, err := os.OpenFile(fname, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return
}
defer out.Close()
_, err = out.Write(source)
return
}
func (g *Generator) generateSpec(spec Spec) (err error) {
switch spec.Type {
case "interface":
err = g.generateInterface(spec)
case "interface mixin":
// fmt.Printf("INTERFACE MIXIN - %s\r\n", spec.Name)
case "callback":
err = g.generateCallback(spec)
case "dictionary":
// fmt.Printf("DICTIONARY - %s\r\n", spec.Name)
case "enum":
err = g.generateEnum(spec)
case "typedef":
err = g.generateTypedef(spec)
case "callback interface":
err = g.generateCallbackInterface(spec)
case "namespace":
err = g.generateNamespace(spec)
default:
err = fmt.Errorf("Unknown or unsupported spec type %q", spec.Type)
}
if spec.Name == "Document" || spec.Name == "Window" {
if err == nil {
err = g.generateNamespace(spec)
}
}
return
}
func (g *Generator) Generate() error {
for _, spec := range g.specs {
err := g.generateSpec(spec)
if err != nil {
return err
}
}
return nil
}