-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
schema.go
205 lines (186 loc) · 5.48 KB
/
schema.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package tl
import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
)
// SchemaDefinition is annotated Definition with Category.
type SchemaDefinition struct {
Annotations []Annotation `json:"annotations,omitempty"` // annotations (comments)
Definition Definition `json:"definition"` // definition
Category Category `json:"category"` // category of definition (function or type)
}
// Class describes a non-bare Type with one or more constructors.
//
// Example: `//@class InputChatPhoto @description Describes input chat photo`.
type Class struct {
Name string
Description string
}
// Schema represents single TL file with information about definitions and
// so-called "Classes" aka non-bare types with one or multiple constructors.
type Schema struct {
Layer int `json:"layer,omitempty"`
Definitions []SchemaDefinition `json:"definitions"`
Classes []Class `json:"classes,omitempty"`
}
// WriteTo writes whole schema to w, implementing io.WriterTo.
func (s Schema) WriteTo(w io.Writer) (int64, error) {
classes := make(map[string]Class)
classDefined := make(map[string]struct{})
for _, class := range s.Classes {
classes[class.Name] = class
}
category := CategoryType
// Probably we can write to w directly, but schemas that are larger than
// few megs are a problem itself.
var b strings.Builder
for _, d := range s.Definitions {
if d.Category != category {
category = d.Category
b.WriteString("\n")
switch category {
case CategoryType:
b.WriteString(tokTypes)
case CategoryFunction:
b.WriteString(tokFunctions)
}
b.WriteString("\n\n")
}
if class, exist := classes[d.Definition.Type.Name]; exist {
// Describing class if not already defined.
if _, defined := classDefined[class.Name]; !defined {
b.WriteString(singleLineAnnotations([]Annotation{
{Name: AnnotationClass, Value: class.Name},
{Name: AnnotationDescription, Value: class.Description},
}))
classDefined[class.Name] = struct{}{}
b.WriteString("\n\n")
}
}
for _, a := range d.Annotations {
b.WriteString(a.String())
b.WriteString("\n")
}
// Writing definition itself.
b.WriteString(d.Definition.String())
b.WriteString(";\n\n")
}
if s.Layer != 0 {
b.WriteString(fmt.Sprintf("// LAYER %d\n", s.Layer))
}
n, err := w.Write([]byte(b.String()))
return int64(n), err
}
const (
vectorDefinition = "vector {t:Type} # [ t ] = Vector t;"
vectorDefinitionWithID = "vector#1cb5c415 {t:Type} # [ t ] = Vector t;"
)
// Parse reads Schema from reader.
//
// Can return i/o or validation error.
func Parse(reader io.Reader) (*Schema, error) {
var (
def SchemaDefinition
line int
category = CategoryType
schema = &Schema{}
scanner = bufio.NewScanner(reader)
)
for scanner.Scan() {
line++
s := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(s, "///") {
s = s[1:] // normalize comments
}
switch s {
case "":
continue
case tokFunctions:
category = CategoryFunction
continue
case tokTypes:
category = CategoryType
continue
case vectorDefinition, vectorDefinitionWithID:
// Special case for vector.
continue
}
if strings.HasPrefix(s, tokLayer) {
// Layer version annotation.
layer, err := strconv.Atoi(strings.TrimPrefix(s, tokLayer))
if err != nil {
return nil, fmt.Errorf("failed to parse layer: %w", err)
}
schema.Layer = layer
}
if strings.HasPrefix(s, "//@") {
// Found annotation.
ann, err := parseAnnotation(s)
if err != nil {
return nil, fmt.Errorf("failed to parse line %d: %w", line, err)
}
if strings.HasPrefix(s, "//@"+AnnotationClass) {
// Handling class annotation as special case.
var class Class
for _, a := range ann {
if a.Name == AnnotationClass {
class.Name = a.Value
}
if a.Name == AnnotationDescription {
class.Description = a.Value
}
}
if class.Name != "" && class.Description != "" {
schema.Classes = append(schema.Classes, class)
}
// Reset annotations so we don't include them to next type.
ann = ann[:0]
}
def.Annotations = append(def.Annotations, ann...)
continue // annotation is parsed, moving to next line
}
if strings.HasPrefix(s, "//") {
continue // skip comments
}
// Type definition started.
def.Category = category
if err := def.Definition.Parse(s); err != nil {
return nil, fmt.Errorf("failed to parse line %d: definition: %w", line, err)
}
// Validating annotations.
paramExist := map[string]struct{}{}
for _, p := range def.Definition.Params {
paramExist[p.Name] = struct{}{}
}
for _, ann := range def.Annotations {
if ann.Name == AnnotationDescription {
continue
}
searchFor := ann.Name
if ann.Name == AnnotationParamDescription {
// Special case for "description" parameter name that collides
// with global description.
searchFor = "description"
}
if _, ok := paramExist[searchFor]; !ok {
// Probably such errors can be just skipped, but seems like it
// is OK to consider this as hard failure.
return nil, fmt.Errorf("failed to parse line %d: "+
"can't find param for annotation %q", line, ann.Name)
}
}
schema.Definitions = append(schema.Definitions, def)
def = SchemaDefinition{} // reset definition
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("failed to scan: %w", err)
}
// Remaining type.
if def.Definition.ID != 0 {
schema.Definitions = append(schema.Definitions, def)
}
return schema, nil
}