meta/idl/idl.go

Summary

Maintainability
A
40 mins
Test Coverage
B
88%
package idl

import (
    "fmt"
    "io"

    "github.com/lugu/qiloop/meta/signature"
    "github.com/lugu/qiloop/type/object"
)

// generateMethod writes the method declaration. Does not use
// methodName, because is the Go method name generated to avoid
// conflicts. QiMessage do not have such constraint and thus we don't
// use this name when creating IDL files.
func generateMethod(writer io.Writer, set *signature.TypeSet,
    m object.MetaMethod, methodName string) error {

    // paramType is a tuple it needs to be unified with
    // m.MetaMethodParameter.
    paramType, err := signature.Parse(m.ParametersSignature)
    if err != nil {
        return fmt.Errorf("parse parms of %s: %s", m.Name, err)
    }
    retType, err := signature.Parse(m.ReturnSignature)
    if err != nil {
        return fmt.Errorf("parse return of %s: %s", m.Name, err)
    }

    tupleType, ok := paramType.(*signature.TupleType)
    if !ok {
        // some buggy service don' t return tupples
        tupleType = signature.NewTupleType([]signature.Type{paramType})
    }

    paramSignature := ""
    if m.Parameters == nil || len(m.Parameters) != len(tupleType.Members) {
        paramSignature = tupleType.ParamIDL()
    } else {
        for i, p := range m.Parameters {
            if paramSignature != "" {
                paramSignature += ","
            }
            name := signature.CleanVarName(i, p.Name)
            paramSignature += name + ": " + tupleType.Members[i].Type.SignatureIDL()
        }
    }

    returnSignature := "-> " + retType.SignatureIDL() + " "
    if retType.Signature() == "v" {
        returnSignature = ""
    }

    fmt.Fprintf(writer, "\tfn %s(%s) %s//uid:%d\n", m.Name, paramSignature, returnSignature, m.Uid)

    paramType.RegisterTo(set)
    retType.RegisterTo(set)
    return nil
}

// generateProperties writes the signal declaration. Does not use
// methodName, because is the Go method name generated to avoid
// conflicts. QiMessage do not have such constraint and thus we don't
// use this name when creating IDL files.
func generateProperty(writer io.Writer, set *signature.TypeSet,
    p object.MetaProperty, propertyName string) error {

    propertyType, err := signature.Parse(p.Signature)
    if err != nil {
        return fmt.Errorf("parse property of %s: %s", p.Name, err)
    }
    propertyType.RegisterTo(set)

    tupleType, ok := propertyType.(*signature.TupleType)
    if !ok {
        tupleType = &signature.TupleType{
            Members: []signature.MemberType{
                signature.MemberType{
                    Name: "param",
                    Type: propertyType,
                },
            },
        }
    }

    fmt.Fprintf(writer, "\tprop %s(%s) //uid:%d\n", p.Name,
        tupleType.ParamIDL(), p.Uid)
    return nil
}

// generateSignal writes the signal declaration. Does not use
// methodName, because is the Go method name generated to avoid
// conflicts. QiMessage do not have such constraint and thus we don't
// use this name when creating IDL files.
func generateSignal(writer io.Writer, set *signature.TypeSet, s object.MetaSignal, methodName string) error {
    signalType, err := signature.Parse(s.Signature)
    if err != nil {
        return fmt.Errorf("parse signal of %s: %s", s.Name, err)
    }
    signalType.RegisterTo(set)

    tupleType, ok := signalType.(*signature.TupleType)
    if !ok {
        tupleType = signature.NewTupleType([]signature.Type{signalType})
    }
    fmt.Fprintf(writer, "\tsig %s(%s) //uid:%d\n", s.Name, tupleType.ParamIDL(), s.Uid)
    return nil
}

func generateStructure(writer io.Writer, s *signature.StructType) error {
    fmt.Fprintf(writer, "struct %s\n", s.Name)
    for _, mem := range s.Members {
        fmt.Fprintf(writer, "\t%s: %s\n", mem.Name, mem.Type.SignatureIDL())
    }
    fmt.Fprintf(writer, "end\n")
    return nil
}

func generateStructures(writer io.Writer, set *signature.TypeSet) error {
    for _, typ := range set.Types {
        if structure, ok := typ.(*signature.StructType); ok {
            generateStructure(writer, structure)
        }
    }
    return nil
}

// GenerateIDL writes the IDL definition for the meta object in objs.  a MetaObject into a
// writer. This IDL definition can be used to re-create the MetaObject
// with the method ParseIDL.
func GenerateIDL(writer io.Writer, packageName string, objs map[string]object.MetaObject) error {
    set := signature.NewTypeSet()

    fmt.Fprintf(writer, "package %s\n", packageName)
    scope := NewScope()

    for name, meta := range objs {

        // register service name to avoid collision with structs
        // FIXME: should register an InterfaceType and not a RefType
        name = set.ResolveCollision(name, "o")
        set.Types = append(set.Types, NewRefType(name, scope))
        set.Names = append(set.Names, name)

        fmt.Fprintf(writer, "interface %s\n", name)

        method := func(m object.MetaMethod, methodName string) error {
            return generateMethod(writer, set, m, methodName)
        }
        signal := func(s object.MetaSignal, signalName string) error {
            return generateSignal(writer, set, s, "Subscribe"+signalName)
        }
        property := func(p object.MetaProperty, propertyName string) error {
            return generateProperty(writer, set, p, propertyName)
        }

        if err := meta.ForEachMethodAndSignal(method, signal, property); err != nil {
            return fmt.Errorf("generate proxy object %s: %s", name, err)
        }
        fmt.Fprintf(writer, "end\n")
    }

    if err := generateStructures(writer, set); err != nil {
        return fmt.Errorf("generate structures: %s", err)
    }
    return nil
}