summaryrefslogtreecommitdiff
path: root/parser/type.go
blob: 4858d7535e2ea66cbf3fe13d5cbe2227bb15d51f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package parser

import (
	"fmt"
	"log"
	"reflect"

	"github.com/gnolang/parscan/lang"
)

// ParseType parses a list of tokens defining a type expresssion and returns
// the corresponding runtime type or an error.
func (p *Parser) ParseType(in Tokens) (typ reflect.Type, err error) {
	log.Println("ParseType", in)
	switch in[0].Id {
	case lang.Func:
		// Get argument and return token positions depending on function pattern:
		// method with receiver, named function or anonymous closure.
		// TODO: handle variadics
		var out Tokens
		var indexArgs int
		switch l, in1 := len(in), in[1]; {
		case l >= 4 && in1.Id == lang.ParenBlock && in[2].Id == lang.Ident:
			indexArgs, out = 3, in[4:]
		case l >= 3 && in1.Id == lang.Ident:
			indexArgs, out = 2, in[3:]
		case l >= 2 && in1.Id == lang.ParenBlock:
			indexArgs, out = 1, in[2:]
		default:
			return nil, fmt.Errorf("invalid func signature")
		}

		// We can now parse function input and output parameter types.
		// Input parameters are always enclosed by parenthesis.
		iargs, err := p.Scan(in[indexArgs].Block(), false)
		if err != nil {
			return nil, err
		}
		arg, err := p.parseParamTypes(iargs, true)
		if err != nil {
			return nil, err
		}
		// Output parameters may be empty, or enclosed or not by parenthesis.
		if len(out) == 1 && out[0].Id == lang.ParenBlock {
			if out, err = p.Scan(out[0].Block(), false); err != nil {
				return nil, err
			}
		}
		ret, err := p.parseParamTypes(out, false)
		if err != nil {
			return nil, err
		}
		return reflect.FuncOf(arg, ret, false), nil

	case lang.Ident:
		// TODO: selector expression (pkg.type)
		s, _, ok := p.getSym(in[0].Str, p.scope)
		if !ok || s.kind != symType {
			return nil, fmt.Errorf("invalid type %s", in[0].Str)
		}
		return s.Type, nil
	}
	return typ, err
}

// parseParamTypes parses a list of comma separated typed parameters and returns a list of
// runtime types. Implicit parameter names and types are supported.
func (p *Parser) parseParamTypes(in Tokens, arg bool) (types []reflect.Type, err error) {
	// Parse from right to left, to allow multiple comma separated parameters of the same type.
	list := in.Split(lang.Comma)
	for i := len(list) - 1; i >= 0; i-- {
		t := list[i]
		if len(t) == 0 {
			continue
		}
		param := ""
		if p.hasFirstParam(t) {
			param = t[0].Str
			t = t[1:]
			if len(t) == 0 {
				if len(types) == 0 {
					return nil, fmt.Errorf("Invalid type %v", t[0])
				}
				// Type was ommitted, apply the previous one from the right.
				types = append([]reflect.Type{types[0]}, types...)
				if arg {
					p.addSym(-i-2, p.scope+"/"+param, nil, symVar, types[0], true)
				} else {
					p.addSym(i, p.scope+"/"+param, nil, symVar, types[0], true)
				}
				continue
			}
		}
		typ, err := p.ParseType(t)
		if err != nil {
			return nil, err
		}
		if param != "" {
			if arg {
				p.addSym(-i-2, p.scope+"/"+param, nil, symVar, typ, true)
			} else {
				p.addSym(i, p.scope+"/"+param, nil, symVar, typ, true)
			}
		}
		types = append([]reflect.Type{typ}, types...)
	}
	return types, err
}

// hasFirstParam returns true if the first token of a list is a parameter name.
func (p *Parser) hasFirstParam(in Tokens) bool {
	if in[0].Id != lang.Ident {
		return false
	}
	s, _, ok := p.getSym(in[0].Str, p.scope)
	return !ok || s.kind != symType
}