forked from kataras/iris
-
Notifications
You must be signed in to change notification settings - Fork 0
/
parser.go
192 lines (166 loc) · 5.49 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
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
package parser
import (
"fmt"
"strconv"
"strings"
"github.com/kataras/iris/macro/interpreter/ast"
"github.com/kataras/iris/macro/interpreter/lexer"
"github.com/kataras/iris/macro/interpreter/token"
)
// Parse takes a route "fullpath"
// and returns its param statements
// or an error if failed.
func Parse(fullpath string, paramTypes []ast.ParamType) ([]*ast.ParamStatement, error) {
if len(paramTypes) == 0 {
return nil, fmt.Errorf("empty parameter types")
}
pathParts := strings.SplitN(fullpath, "/", -1)
p := new(ParamParser)
statements := make([]*ast.ParamStatement, 0)
for i, s := range pathParts {
if s == "" { // if starts with /
continue
}
// if it's not a named path parameter of the new syntax then continue to the next
if s[0] != lexer.Begin || s[len(s)-1] != lexer.End {
continue
}
p.Reset(s)
stmt, err := p.Parse(paramTypes)
if err != nil {
// exit on first error
return nil, err
}
// if we have param type path but it's not the last path part
if ast.IsTrailing(stmt.Type) && i < len(pathParts)-1 {
return nil, fmt.Errorf("%s: parameter type \"%s\" should be registered to the very last of a path", s, stmt.Type.Indent())
}
statements = append(statements, stmt)
}
return statements, nil
}
// ParamParser is the parser
// which is being used by the Parse function
// to parse path segments one by one
// and return their parsed parameter statements (param name, param type its functions and the inline route's functions).
type ParamParser struct {
src string
errors []string
}
// NewParamParser receives a "src" of a single parameter
// and returns a new ParamParser, ready to Parse.
func NewParamParser(src string) *ParamParser {
p := new(ParamParser)
p.Reset(src)
return p
}
// Reset resets this ParamParser,
// reset the errors and set the source to the input "src".
func (p *ParamParser) Reset(src string) {
p.src = src
p.errors = []string{}
}
func (p *ParamParser) appendErr(format string, a ...interface{}) {
p.errors = append(p.errors, fmt.Sprintf(format, a...))
}
const (
// DefaultParamErrorCode is the default http error code, 404 not found,
// per-parameter. An error code can be setted via
// the "else" keyword inside a route's path.
DefaultParamErrorCode = 404
)
// func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) {
// if t.Type == token.INT {
// return ast.ParamFuncArgToInt(t.Literal)
// }
// // act all as strings here, because of int vs int64 vs uint64 and etc.
// return t.Literal, nil
// }
func parseParamFuncArg(t token.Token) (a string, err error) {
// act all as strings here, because of int vs int64 vs uint64 and etc.
return t.Literal, nil
}
func (p ParamParser) Error() error {
if len(p.errors) > 0 {
return fmt.Errorf(strings.Join(p.errors, "\n"))
}
return nil
}
// Parse parses the p.src based on the given param types and returns its param statement
// and an error on failure.
func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, error) {
l := lexer.New(p.src)
stmt := &ast.ParamStatement{
ErrorCode: DefaultParamErrorCode,
Type: ast.GetMasterParamType(paramTypes...),
Src: p.src,
}
lastParamFunc := ast.ParamFunc{}
for {
t := l.NextToken()
if t.Type == token.EOF {
if stmt.Name == "" {
p.appendErr("[1:] parameter name is missing")
}
break
}
switch t.Type {
case token.LBRACE:
// can accept only letter or number only.
nextTok := l.NextToken()
stmt.Name = nextTok.Literal
case token.COLON:
// type can accept both letters and numbers but not symbols ofc.
nextTok := l.NextToken()
paramType, found := ast.LookupParamType(nextTok.Literal, paramTypes...)
if !found {
p.appendErr("[%d:%d] unexpected parameter type: %s", t.Start, t.End, nextTok.Literal)
}
stmt.Type = paramType
// param func
case token.IDENT:
lastParamFunc.Name = t.Literal
case token.LPAREN:
// param function without arguments ()
if l.PeekNextTokenType() == token.RPAREN {
// do nothing, just continue to the RPAREN
continue
}
argValTok := l.NextDynamicToken() // catch anything from "(" and forward, until ")", because we need to
// be able to use regex expression as a macro type's func argument too.
// fmt.Printf("argValTok: %#v\n", argValTok)
// fmt.Printf("argVal: %#v\n", argVal)
lastParamFunc.Args = append(lastParamFunc.Args, argValTok.Literal)
case token.COMMA:
argValTok := l.NextToken()
lastParamFunc.Args = append(lastParamFunc.Args, argValTok.Literal)
case token.RPAREN:
stmt.Funcs = append(stmt.Funcs, lastParamFunc)
lastParamFunc = ast.ParamFunc{} // reset
case token.ELSE:
errCodeTok := l.NextToken()
if errCodeTok.Type != token.INT {
p.appendErr("[%d:%d] expected error code to be an integer but got %s", t.Start, t.End, errCodeTok.Literal)
continue
}
errCode, err := strconv.Atoi(errCodeTok.Literal)
if err != nil {
// this is a bug on lexer if throws because we already check for token.INT
p.appendErr("[%d:%d] unexpected lexer error while trying to convert error code to an integer, %s", t.Start, t.End, err.Error())
continue
}
stmt.ErrorCode = errCode
case token.RBRACE:
// check if } but not {
if stmt.Name == "" {
p.appendErr("[%d:%d] illegal token: }, forgot '{' ?", t.Start, t.End)
}
break
case token.ILLEGAL:
p.appendErr("[%d:%d] illegal token: %s", t.Start, t.End, t.Literal)
default:
p.appendErr("[%d:%d] unexpected token type: %q with value %s", t.Start, t.End, t.Type, t.Literal)
}
}
return stmt, p.Error()
}