-
Notifications
You must be signed in to change notification settings - Fork 0
/
definition.go
155 lines (150 loc) · 4.29 KB
/
definition.go
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package tl
import (
"errors"
"fmt"
"hash/crc32"
"strconv"
"strings"
"unicode"
)
// Definition represents "Type Language" definition.
//
// See https://core.telegram.org/mtproto/TL for reference.
type Definition struct {
Namespace []string `json:"namespace,omitempty"` // blank if global
Name string `json:"name"` // name of definition, aka "predicate" or "method"
ID uint32 `json:"id"` // crc32(definition) or explicitly specified
Params []Parameter `json:"params,omitempty"` // can be empty
Type Type `json:"type"` // type of definition
Base bool `json:"base,omitempty"` // base type?
GenericParams []string `json:"generic_params,omitempty"` // like {T:Type}
}
func (d Definition) String() string {
var b strings.Builder
for _, ns := range d.Namespace {
b.WriteString(ns)
b.WriteRune('.')
}
b.WriteString(fmt.Sprintf("%s#%x", d.Name, d.ID))
for _, param := range d.GenericParams {
b.WriteString(" {")
b.WriteString(param)
b.WriteString(":Type}")
}
for _, param := range d.Params {
b.WriteRune(' ')
b.WriteString(param.String())
}
if d.Base {
b.WriteString(" ?")
}
b.WriteString(" = ")
b.WriteString(d.Type.String())
return b.String()
}
// Parse TL definition line like `foo#123 code:int name:string = Message;`.
func (d *Definition) Parse(line string) error {
line = strings.TrimRight(line, ";")
parts := strings.Split(line, "=")
if len(parts) != 2 {
return errors.New("unexpected definition elements")
}
// Splitting definition line into left and right parts.
// Example: `foo#123 code:int name:string = Message`
var (
left = strings.TrimSpace(parts[0]) // `foo#123 code:int name:string`
right = strings.TrimSpace(parts[1]) // `Message`
// Divided left part elements, like []{"foo#123", "code:int", "name:string"}
leftParts = strings.Split(left, " ")
)
if left == "" || right == "" {
return errors.New("definition part is blank")
}
if err := d.Type.Parse(right); err != nil {
return fmt.Errorf("failed to parse type: %w", err)
}
{
// Parsing definition name and id.
first := leftParts[0]
nameParts := strings.SplitN(first, tokID, 2)
d.Name = nameParts[0]
if d.Name == "" {
return errors.New("blank name")
}
if len(nameParts) > 1 {
// Parsing definition id as hex to uint32.
idHex := nameParts[1]
id, err := strconv.ParseUint(idHex, 16, 32)
if err != nil {
return fmt.Errorf("%s is invalid id: %w", idHex, err)
}
d.ID = uint32(id)
} else {
// Automatically computing.
d.ID = crc32.ChecksumIEEE([]byte(line))
}
if nsParts := strings.Split(d.Name, "."); len(nsParts) > 1 {
// Handling definition namespace.
d.Name = nsParts[len(nsParts)-1]
d.Namespace = nsParts[:len(nsParts)-1]
}
for _, ns := range d.Namespace {
if !isValidName(ns) {
return fmt.Errorf("invalid namespace part %q", ns)
}
}
}
genericParams := map[string]struct{}{}
for _, f := range leftParts[1:] {
// Parsing parameters.
if f == "?" {
// Special case.
d.Base = true
continue
}
var param Parameter
if err := param.Parse(f); err != nil {
return fmt.Errorf("failed to parse param: %w", err)
}
// Handling generics.
// Example:
// `t#1 {X:Type} x:!X = X;` is valid type definition with generic type "X".
// Type of parameter "x" is {Name: "X", GenericRef: true}.
if param.typeDefinition {
// Parameter is generic type definition like {T:Type}.
genericParams[param.Name] = struct{}{}
d.GenericParams = append(d.GenericParams, param.Name)
continue // not adding generic to actual params
}
// Checking that type of generic parameter was defined.
// E.g. `t#1 {Y:Type} x:!X = X;` is invalid, because X was not defined.
if param.Type.GenericRef {
if _, ok := genericParams[param.Type.Name]; !ok {
return fmt.Errorf("undefined generic parameter type %s", param.Type.Name)
}
}
d.Params = append(d.Params, param)
}
if !isValidName(d.Name) {
return fmt.Errorf("invalid name %q", d.Name)
}
return nil
}
func isValidName(name string) bool {
if name == "" {
return false
}
for _, s := range name {
if unicode.IsDigit(s) {
continue
}
if unicode.IsLetter(s) {
continue
}
if s == '_' {
continue
}
return false
}
return true
}