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: 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)
|
|
|
|
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)
|
|
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
|
|
}
|