generated from TBD54566975/tbd-project-template
/
parser.go
141 lines (117 loc) 路 3.34 KB
/
parser.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
package compile
import (
"errors"
"fmt"
"go/ast"
"go/token"
"strings"
"github.com/alecthomas/participle/v2"
"github.com/alecthomas/participle/v2/lexer"
"github.com/TBD54566975/ftl/backend/schema"
)
// This file contains a parser for Go FTL directives.
//
// eg. //ftl:ingress http GET /foo/bar
type directiveWrapper struct {
Directive directive `parser:"'ftl' ':' @@"`
}
//sumtype:decl
type directive interface{ directive() }
type directiveVerb struct {
Pos lexer.Position
Verb bool `parser:"@'verb'"`
Export bool `parser:"@'export'?"`
}
func (*directiveVerb) directive() {}
func (d *directiveVerb) String() string {
if d.Export {
return "ftl:verb export"
}
return "ftl:verb"
}
type directiveData struct {
Pos lexer.Position
Data bool `parser:"@'data'"`
Export bool `parser:"@'export'?"`
}
func (*directiveData) directive() {}
func (d *directiveData) String() string {
if d.Export {
return "ftl:data export"
}
return "ftl:data"
}
type directiveEnum struct {
Pos lexer.Position
Enum bool `parser:"@'enum'"`
Export bool `parser:"@'export'?"`
}
func (*directiveEnum) directive() {}
func (d *directiveEnum) String() string {
if d.Export {
return "ftl:enum export"
}
return "ftl:enum"
}
type directiveIngress struct {
Pos schema.Position
Type string `parser:"'ingress' @('http')?"`
Method string `parser:"@('GET' | 'POST' | 'PUT' | 'DELETE')"`
Path []schema.IngressPathComponent `parser:"('/' @@)+"`
}
func (*directiveIngress) directive() {}
func (d *directiveIngress) String() string {
w := &strings.Builder{}
fmt.Fprintf(w, "ftl:ingress %s", d.Method)
for _, p := range d.Path {
fmt.Fprintf(w, "/%s", p)
}
return w.String()
}
type directiveCronJob struct {
Pos schema.Position
Cron string `parser:"'cron' Whitespace @((' ' | Number | '-' | '/' | '*' | ',')+)"`
}
func (*directiveCronJob) directive() {}
func (d *directiveCronJob) String() string {
return fmt.Sprintf("cron %s", d.Cron)
}
var directiveParser = participle.MustBuild[directiveWrapper](
participle.Lexer(schema.Lexer),
participle.Elide("Whitespace"),
participle.Unquote(),
participle.UseLookahead(2),
participle.Union[directive](&directiveVerb{}, &directiveData{}, &directiveEnum{}, &directiveIngress{}, &directiveCronJob{}),
participle.Union[schema.IngressPathComponent](&schema.IngressPathLiteral{}, &schema.IngressPathParameter{}),
)
func parseDirectives(node ast.Node, fset *token.FileSet, docs *ast.CommentGroup) ([]directive, *schema.Error) {
if docs == nil {
return nil, nil
}
directives := []directive{}
for _, line := range docs.List {
if !strings.HasPrefix(line.Text, "//ftl:") {
continue
}
pos := fset.Position(line.Pos())
// TODO: We need to adjust position information embedded in the schema.
directive, err := directiveParser.ParseString(pos.Filename, line.Text[2:])
if err != nil {
// Adjust the Participle-reported position relative to the AST node.
var scerr *schema.Error
var perr participle.Error
if errors.As(err, &perr) {
ppos := schema.Position{}
ppos.Filename = pos.Filename
ppos.Column += pos.Column + 2
ppos.Line = pos.Line
scerr = schema.Errorf(ppos, ppos.Column, "%s", perr.Message())
} else {
scerr = wrapf(node, err, "")
}
return nil, scerr
}
directives = append(directives, directive.Directive)
}
return directives, nil
}