|
@@ -0,0 +1,219 @@
|
|
|
+package parser
|
|
|
+
|
|
|
+import (
|
|
|
+ "bufio"
|
|
|
+ "bytes"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "strings"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ tokenInfo = "info"
|
|
|
+ tokenImport = "import"
|
|
|
+ tokenType = "type"
|
|
|
+ tokenService = "service"
|
|
|
+ tokenServiceAnnotation = "@server"
|
|
|
+)
|
|
|
+
|
|
|
+type (
|
|
|
+ ApiStruct struct {
|
|
|
+ Info string
|
|
|
+ Type string
|
|
|
+ Service string
|
|
|
+ Imports string
|
|
|
+ serviceBeginLine int
|
|
|
+ }
|
|
|
+
|
|
|
+ apiFileState interface {
|
|
|
+ process(api *ApiStruct, token string) (apiFileState, error)
|
|
|
+ }
|
|
|
+
|
|
|
+ apiRootState struct {
|
|
|
+ *baseState
|
|
|
+ }
|
|
|
+
|
|
|
+ apiInfoState struct {
|
|
|
+ *baseState
|
|
|
+ }
|
|
|
+
|
|
|
+ apiImportState struct {
|
|
|
+ *baseState
|
|
|
+ }
|
|
|
+
|
|
|
+ apiTypeState struct {
|
|
|
+ *baseState
|
|
|
+ }
|
|
|
+
|
|
|
+ apiServiceState struct {
|
|
|
+ *baseState
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+func ParseApi(src string) (*ApiStruct, error) {
|
|
|
+ var buffer = new(bytes.Buffer)
|
|
|
+ buffer.WriteString(src)
|
|
|
+ api := new(ApiStruct)
|
|
|
+ var lineNumber = api.serviceBeginLine
|
|
|
+ apiFile := baseState{r: bufio.NewReader(buffer), lineNumber: &lineNumber}
|
|
|
+ st := apiRootState{&apiFile}
|
|
|
+ for {
|
|
|
+ st, err := st.process(api, "")
|
|
|
+ if err == io.EOF {
|
|
|
+ return api, nil
|
|
|
+ }
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("near line: %d, %s", lineNumber, err.Error())
|
|
|
+ }
|
|
|
+ if st == nil {
|
|
|
+ return api, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (s *apiRootState) process(api *ApiStruct, token string) (apiFileState, error) {
|
|
|
+ var builder strings.Builder
|
|
|
+ for {
|
|
|
+ ch, err := s.readSkipComment()
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ switch {
|
|
|
+ case isSpace(ch) || isNewline(ch) || ch == leftParenthesis:
|
|
|
+ token := builder.String()
|
|
|
+ token = strings.TrimSpace(token)
|
|
|
+ if len(token) == 0 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ builder.Reset()
|
|
|
+ switch token {
|
|
|
+ case tokenInfo:
|
|
|
+ info := apiInfoState{s.baseState}
|
|
|
+ return info.process(api, token+string(ch))
|
|
|
+ case tokenImport:
|
|
|
+ tp := apiImportState{s.baseState}
|
|
|
+ return tp.process(api, token+string(ch))
|
|
|
+ case tokenType:
|
|
|
+ ty := apiTypeState{s.baseState}
|
|
|
+ return ty.process(api, token+string(ch))
|
|
|
+ case tokenService:
|
|
|
+ server := apiServiceState{s.baseState}
|
|
|
+ return server.process(api, token+string(ch))
|
|
|
+ case tokenServiceAnnotation:
|
|
|
+ server := apiServiceState{s.baseState}
|
|
|
+ return server.process(api, token+string(ch))
|
|
|
+ default:
|
|
|
+ if strings.HasPrefix(token, "//") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ return nil, errors.New(fmt.Sprintf("invalid token %s at line %d", token, *s.lineNumber))
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ builder.WriteRune(ch)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (s *apiInfoState) process(api *ApiStruct, token string) (apiFileState, error) {
|
|
|
+ for {
|
|
|
+ line, err := s.readLine()
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ api.Info += "\n" + token + line
|
|
|
+ token = ""
|
|
|
+ if strings.TrimSpace(line) == string(rightParenthesis) {
|
|
|
+ return &apiRootState{s.baseState}, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (s *apiImportState) process(api *ApiStruct, token string) (apiFileState, error) {
|
|
|
+ line, err := s.readLine()
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ line = token + line
|
|
|
+ if len(strings.Fields(line)) != 2 {
|
|
|
+ return nil, errors.New("import syntax error: " + line)
|
|
|
+ }
|
|
|
+
|
|
|
+ api.Imports += "\n" + line
|
|
|
+ return &apiRootState{s.baseState}, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (s *apiTypeState) process(api *ApiStruct, token string) (apiFileState, error) {
|
|
|
+ var blockCount = 0
|
|
|
+ for {
|
|
|
+ line, err := s.readLine()
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ api.Type += "\n\n" + token + line
|
|
|
+ token = ""
|
|
|
+ line = strings.TrimSpace(line)
|
|
|
+ line = removeComment(line)
|
|
|
+ if strings.HasSuffix(line, leftBrace) {
|
|
|
+ blockCount++
|
|
|
+ }
|
|
|
+ if strings.HasSuffix(line, string(leftParenthesis)) {
|
|
|
+ blockCount++
|
|
|
+ }
|
|
|
+ if strings.HasSuffix(line, string(rightBrace)) {
|
|
|
+ blockCount--
|
|
|
+ }
|
|
|
+ if strings.HasSuffix(line, string(rightParenthesis)) {
|
|
|
+ blockCount--
|
|
|
+ }
|
|
|
+
|
|
|
+ if blockCount == 0 {
|
|
|
+ return &apiRootState{s.baseState}, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (s *apiServiceState) process(api *ApiStruct, token string) (apiFileState, error) {
|
|
|
+ var blockCount = 0
|
|
|
+ for {
|
|
|
+ line, err := s.readLineSkipComment()
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ line = token + line
|
|
|
+ token = ""
|
|
|
+ api.Service += "\n" + line
|
|
|
+ line = strings.TrimSpace(line)
|
|
|
+ line = removeComment(line)
|
|
|
+ if strings.HasSuffix(line, leftBrace) {
|
|
|
+ blockCount++
|
|
|
+ }
|
|
|
+ if strings.HasSuffix(line, string(leftParenthesis)) {
|
|
|
+ blockCount++
|
|
|
+ }
|
|
|
+ if line == string(rightBrace) {
|
|
|
+ blockCount--
|
|
|
+ }
|
|
|
+ if line == string(rightParenthesis) {
|
|
|
+ blockCount--
|
|
|
+ }
|
|
|
+
|
|
|
+ if blockCount == 0 {
|
|
|
+ return &apiRootState{s.baseState}, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func removeComment(line string) string {
|
|
|
+ var commentIdx = strings.Index(line, "//")
|
|
|
+ if commentIdx >= 0 {
|
|
|
+ return line[:commentIdx]
|
|
|
+ }
|
|
|
+ return line
|
|
|
+}
|