parser.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package parser
  2. import (
  3. "fmt"
  4. "strconv"
  5. "strings"
  6. "github.com/kataras/iris/macro/interpreter/ast"
  7. "github.com/kataras/iris/macro/interpreter/lexer"
  8. "github.com/kataras/iris/macro/interpreter/token"
  9. )
  10. // Parse takes a route "fullpath"
  11. // and returns its param statements
  12. // or an error if failed.
  13. func Parse(fullpath string, paramTypes []ast.ParamType) ([]*ast.ParamStatement, error) {
  14. if len(paramTypes) == 0 {
  15. return nil, fmt.Errorf("empty parameter types")
  16. }
  17. pathParts := strings.SplitN(fullpath, "/", -1)
  18. p := new(ParamParser)
  19. statements := make([]*ast.ParamStatement, 0)
  20. for i, s := range pathParts {
  21. if s == "" { // if starts with /
  22. continue
  23. }
  24. // if it's not a named path parameter of the new syntax then continue to the next
  25. if s[0] != lexer.Begin || s[len(s)-1] != lexer.End {
  26. continue
  27. }
  28. p.Reset(s)
  29. stmt, err := p.Parse(paramTypes)
  30. if err != nil {
  31. // exit on first error
  32. return nil, err
  33. }
  34. // if we have param type path but it's not the last path part
  35. if ast.IsTrailing(stmt.Type) && i < len(pathParts)-1 {
  36. return nil, fmt.Errorf("%s: parameter type \"%s\" should be registered to the very last of a path", s, stmt.Type.Indent())
  37. }
  38. statements = append(statements, stmt)
  39. }
  40. return statements, nil
  41. }
  42. // ParamParser is the parser
  43. // which is being used by the Parse function
  44. // to parse path segments one by one
  45. // and return their parsed parameter statements (param name, param type its functions and the inline route's functions).
  46. type ParamParser struct {
  47. src string
  48. errors []string
  49. }
  50. // NewParamParser receives a "src" of a single parameter
  51. // and returns a new ParamParser, ready to Parse.
  52. func NewParamParser(src string) *ParamParser {
  53. p := new(ParamParser)
  54. p.Reset(src)
  55. return p
  56. }
  57. // Reset resets this ParamParser,
  58. // reset the errors and set the source to the input "src".
  59. func (p *ParamParser) Reset(src string) {
  60. p.src = src
  61. p.errors = []string{}
  62. }
  63. func (p *ParamParser) appendErr(format string, a ...interface{}) {
  64. p.errors = append(p.errors, fmt.Sprintf(format, a...))
  65. }
  66. const (
  67. // DefaultParamErrorCode is the default http error code, 404 not found,
  68. // per-parameter. An error code can be set via
  69. // the "else" keyword inside a route's path.
  70. DefaultParamErrorCode = 404
  71. )
  72. func (p ParamParser) Error() error {
  73. if len(p.errors) > 0 {
  74. return fmt.Errorf(strings.Join(p.errors, "\n"))
  75. }
  76. return nil
  77. }
  78. // Parse parses the p.src based on the given param types and returns its param statement
  79. // and an error on failure.
  80. func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, error) {
  81. l := lexer.New(p.src)
  82. stmt := &ast.ParamStatement{
  83. ErrorCode: DefaultParamErrorCode,
  84. Type: ast.GetMasterParamType(paramTypes...),
  85. Src: p.src,
  86. }
  87. lastParamFunc := ast.ParamFunc{}
  88. for {
  89. t := l.NextToken()
  90. if t.Type == token.EOF {
  91. if stmt.Name == "" {
  92. p.appendErr("[1:] parameter name is missing")
  93. }
  94. break
  95. }
  96. switch t.Type {
  97. case token.LBRACE:
  98. // can accept only letter or number only.
  99. nextTok := l.NextToken()
  100. stmt.Name = nextTok.Literal
  101. case token.COLON:
  102. // type can accept both letters and numbers but not symbols ofc.
  103. nextTok := l.NextToken()
  104. paramType, found := ast.LookupParamType(nextTok.Literal, paramTypes...)
  105. if !found {
  106. p.appendErr("[%d:%d] unexpected parameter type: %s", t.Start, t.End, nextTok.Literal)
  107. }
  108. stmt.Type = paramType
  109. // param func
  110. case token.IDENT:
  111. lastParamFunc.Name = t.Literal
  112. case token.LPAREN:
  113. // param function without arguments ()
  114. if l.PeekNextTokenType() == token.RPAREN {
  115. // do nothing, just continue to the RPAREN
  116. continue
  117. }
  118. argValTok := l.NextDynamicToken() // catch anything from "(" and forward, until ")", because we need to
  119. // be able to use regex expression as a macro type's func argument too.
  120. // fmt.Printf("argValTok: %#v\n", argValTok)
  121. // fmt.Printf("argVal: %#v\n", argVal)
  122. lastParamFunc.Args = append(lastParamFunc.Args, argValTok.Literal)
  123. case token.COMMA:
  124. argValTok := l.NextToken()
  125. lastParamFunc.Args = append(lastParamFunc.Args, argValTok.Literal)
  126. case token.RPAREN:
  127. stmt.Funcs = append(stmt.Funcs, lastParamFunc)
  128. lastParamFunc = ast.ParamFunc{} // reset
  129. case token.ELSE:
  130. errCodeTok := l.NextToken()
  131. if errCodeTok.Type != token.INT {
  132. p.appendErr("[%d:%d] expected error code to be an integer but got %s", t.Start, t.End, errCodeTok.Literal)
  133. continue
  134. }
  135. errCode, err := strconv.Atoi(errCodeTok.Literal)
  136. if err != nil {
  137. // this is a bug on lexer if throws because we already check for token.INT
  138. p.appendErr("[%d:%d] unexpected lexer error while trying to convert error code to an integer, %s", t.Start, t.End, err.Error())
  139. continue
  140. }
  141. stmt.ErrorCode = errCode
  142. case token.RBRACE:
  143. // check if } but not {
  144. if stmt.Name == "" {
  145. p.appendErr("[%d:%d] illegal token: }, forgot '{' ?", t.Start, t.End)
  146. }
  147. case token.ILLEGAL:
  148. p.appendErr("[%d:%d] illegal token: %s", t.Start, t.End, t.Literal)
  149. default:
  150. p.appendErr("[%d:%d] unexpected token type: %q with value %s", t.Start, t.End, t.Type, t.Literal)
  151. }
  152. }
  153. return stmt, p.Error()
  154. }