meta/signature/signature.go

Summary

Maintainability
A
45 mins
Test Coverage
B
80%
package signature

import (
    "fmt"
    "io"
    "reflect"

    "github.com/dave/jennifer/jen"
    parsec "github.com/prataprc/goparsec"
)

// MetaObjectSignature is the signature of MetaObject. It is used to
// generate the MetaObject struct which is used to generate the
// services. This step is referred as stage 1.
const MetaObjectSignature = "({I(Issss[(ss)<MetaMethodParameter,name,description>]s)<MetaMethod,uid,returnSignature,name,parametersSignature,description,parameters,returnDescription>}{I(Iss)<MetaSignal,uid,name,signature>}{I(Iss)<MetaProperty,uid,name,signature>}s)<MetaObject,methods,signals,properties,description>"

// ObjectSignature is the signature of ObjectReference.
var ObjectSignature = fmt.Sprintf("(%sII)<ObjectReference,metaObject,serviceID,objectID>",
    MetaObjectSignature)

// typeSignature is the signature parser.
var typeSignature parsec.Parser

// Statement is a short version for jen.Statement.
type Statement = jen.Statement

func basicType() parsec.Parser {
    return parsec.OrdChoice(nodifyBasicType,
        parsec.Atom("I", "uint32"),
        parsec.Atom("i", "int32"),
        parsec.Atom("s", "string"),
        parsec.Atom("L", "uint64"),
        parsec.Atom("l", "int64"),
        parsec.Atom("b", "bool"),
        parsec.Atom("f", "float32"),
        parsec.Atom("d", "float64"),
        parsec.Atom("m", "value"),
        parsec.Atom("o", "github.com/lugu/qiloop/type/object.Object"),
        parsec.Atom("X", "interface{}"),
        parsec.Atom("v", "void"),
        parsec.Atom("c", "int8"),
        parsec.Atom("C", "uint8"),
        parsec.Atom("w", "int16"),
        parsec.Atom("W", "uint16"),
    )
}

func typeName() parsec.Parser {
    return parsec.Ident()
}

func structName() parsec.Parser {
    patterns := []string{
        // Allow C++ style name (ex: List<double>)
        `[A-Za-z][0-9a-zA-Z_]*\<[A-Za-z][0-9a-zA-Z_]*>`,
        `[A-Za-z][0-9a-zA-Z_]*`,
    }
    names := []string{
        "structTemplateName",
        "structName",
    }
    return parsec.OrdTokens(patterns, names)
}

// Node is an alias to parsec.ParsecNode
type Node = parsec.ParsecNode

func nodifyBasicType(nodes []Node) Node {
    if len(nodes) != 1 {
        return fmt.Errorf("wrong basic arguments %+v", nodes)
    }
    signature := nodes[0].(*parsec.Terminal).GetValue()
    switch signature {
    case "i":
        return NewIntType()
    case "I":
        return NewUintType()
    case "l":
        return NewLongType()
    case "L":
        return NewULongType()
    case "s":
        return NewStringType()
    case "b":
        return NewBoolType()
    case "f":
        return NewFloatType()
    case "d":
        return NewDoubleType()
    case "v":
        return NewVoidType()
    case "m":
        return NewValueType()
    case "o":
        return NewObjectType()
    case "X":
        return NewUnknownType()
    case "c":
        return NewInt8Type()
    case "C":
        return NewUint8Type()
    case "w":
        return NewInt16Type()
    case "W":
        return NewUint16Type()
    default:
        return fmt.Errorf("wrong signature %s", signature)
    }
}

func extractValue(object interface{}) (Type, error) {
    nodes, ok := object.([]Node)
    if !ok {
        return nil, fmt.Errorf("extraction failed: %+v", reflect.TypeOf(object))
    }
    value, ok := nodes[0].(Type)
    if !ok {
        return nil, fmt.Errorf("extraction failed bis: %+v", reflect.TypeOf(nodes[0]))
    }
    return value, nil
}

func nodifyMap(nodes []Node) Node {
    if len(nodes) != 4 {
        fmt.Printf("wrong map arguments %+v\n", nodes)
    }
    key, err := extractValue(nodes[1])
    if err != nil {
        return fmt.Errorf("key conversion failed: %s", err)
    }
    value, err := extractValue(nodes[2])
    if err != nil {
        return fmt.Errorf("value conversion failed: %s", err)
    }
    return NewMapType(key, value)
}

func nodifyArrayType(nodes []Node) Node {
    if len(nodes) != 3 {
        return fmt.Errorf("wrong arguments %+v", nodes)
    }
    value, err := extractValue(nodes[1])
    if err != nil {
        return fmt.Errorf("value conversion failed: %s", err)
    }
    return NewListType(value)
}

func extractMembersTypes(node Node) ([]Type, error) {
    typeList, ok := node.([]Node)
    if !ok {
        return nil, fmt.Errorf("member type list is not a list: %s", reflect.TypeOf(node))
    }
    types := make([]Type, len(typeList))
    for i := range typeList {
        memberType, err := extractValue(typeList[i])
        if err != nil {
            return nil, fmt.Errorf("member type value conversion failed: %s", err)
        }
        types[i] = memberType
    }
    return types, nil
}

func extractMembersName(node Node) ([]string, error) {
    membersList, ok := node.([]Node)
    if !ok {
        return nil, fmt.Errorf("member name list is not a list: %s", reflect.TypeOf(node))
    }
    names := make([]string, len(membersList))
    for i, n := range membersList {
        memberName, ok := n.(*parsec.Terminal)
        if !ok {
            return nil, fmt.Errorf("convert member names %s", reflect.TypeOf(n))
        }
        names[i] = memberName.GetValue()
    }
    return names, nil
}

func extractMembers(typesNode, namesNode Node) ([]MemberType, error) {

    types, err := extractMembersTypes(typesNode)
    if err != nil {
        return nil, fmt.Errorf("extract members types: %s", err)
    }
    names, err := extractMembersName(namesNode)
    if err != nil {
        return nil, fmt.Errorf("extract members name: %s", err)
    }

    if len(types) != len(names) {
        return nil, fmt.Errorf("member types and names of different size: %+v, %+v", types, names)
    }

    members := make([]MemberType, len(names))

    for i := range types {
        members[i] = NewMemberType(names[i], types[i])
    }
    return members, nil
}

func nodifyTupleType(nodes []Node) Node {

    types, err := extractMembersTypes(nodes[1])
    if err != nil {
        return fmt.Errorf("extract tuple member type: %s", err)
    }

    return NewTupleType(types)
}

func nodifyTypeMember(nodes []Node) Node {
    return nodes[1]
}

func nodifyStrucType(nodes []Node) Node {

    terminal, ok := nodes[4].(*parsec.Terminal)
    if !ok {
        return fmt.Errorf("wrong name %s", reflect.TypeOf(nodes[4]))
    }
    name := terminal.GetValue()
    members, err := extractMembers(nodes[1], nodes[5])
    if err != nil {
        return fmt.Errorf("extract type definition: %s", err)
    }

    return NewStructType(name, members)
}

func init() {

    var arrayType parsec.Parser
    var mapType parsec.Parser
    var structType parsec.Parser
    var tupleType parsec.Parser

    var declarationType = parsec.OrdChoice(nil,
        basicType(), &mapType, &arrayType, &structType, &tupleType)

    arrayType = parsec.And(nodifyArrayType,
        parsec.Atom("[", "MapStart"),
        &declarationType,
        parsec.Atom("]", "MapClose"))

    var listType = parsec.Kleene(nil, &declarationType)

    var typeMemberList = parsec.Kleene(
        nil, parsec.And(
            nodifyTypeMember,
            parsec.Atom(",", "TypeComa"),
            typeName(),
        ))

    tupleType = parsec.And(nodifyTupleType,
        parsec.Atom("(", "TypeParameterStart"),
        &listType,
        parsec.Atom(")", "TypeParameterClose"))

    structType = parsec.And(nodifyStrucType,
        parsec.Atom("(", "TypeParameterStart"),
        &listType,
        parsec.Atom(")", "TypeParameterClose"),
        parsec.Atom("<", "TypeDefinitionStart"),
        structName(),
        &typeMemberList,
        parsec.Atom(">", "TypeDefinitionClose"))

    mapType = parsec.And(nodifyMap,
        parsec.Atom("{", "MapStart"),
        &declarationType, &declarationType,
        parsec.Atom("}", "MapClose"))

    typeSignature = declarationType
}

// Parse reads a signature contained in a string and constructs its
// type representation.
func Parse(input string) (Type, error) {
    text := []byte(input)

    root, rest := typeSignature(parsec.NewScanner(text))
    if root == nil {
        return nil, fmt.Errorf("parse signature: %s", input)
    }
    if !rest.Endof() {
        return nil, fmt.Errorf("Signature not completely parsed: %s", text)
    }
    types, ok := root.([]Node)
    if !ok {
        err, ok := root.(error)
        if !ok {
            return nil, fmt.Errorf("convert array: %+v",
                reflect.TypeOf(root))
        }
        return nil, err
    }
    if len(types) != 1 {
        return nil, fmt.Errorf("did not parse only one type: %+v", root)
    }
    constructor, ok := types[0].(Type)
    if !ok {
        return nil, fmt.Errorf("convert value: %+v",
            reflect.TypeOf(types[0]))
    }
    return constructor, nil
}

// GenerateType generate the code required to serialize the given
// type.
func GenerateType(v Type, packageName string, w io.Writer) error {
    file := jen.NewFile(packageName)
    file.PackageComment("file generated. DO NOT EDIT.")
    set := NewTypeSet()
    v.RegisterTo(set)
    set.Declare(file)
    if err := file.Render(w); err != nil {
        return fmt.Errorf("render %s: %s", v.Signature(), err)
    }
    return nil
}