github.com/vcilabs/webrpc@v0.5.2-0.20201116131534-162e27b1b33b/schema/ridl/parser.go (about)

     1  package ridl
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"reflect"
     9  	"runtime"
    10  	"strings"
    11  )
    12  
    13  const (
    14  	wordEnum    = "enum"
    15  	wordImport  = "import"
    16  	wordMap     = "map"
    17  	wordMessage = "message"
    18  	wordName    = "name"
    19  	wordProxy   = "proxy"
    20  	wordService = "service"
    21  	wordStream  = "stream"
    22  	wordVersion = "version"
    23  	wordWebRPC  = "webrpc"
    24  )
    25  
    26  var (
    27  	errUnexpectedToken = errors.New(`unexpected token`)
    28  	errUnexpectedEOF   = errors.New(`unexpected EOF`)
    29  	errUnexpectedEOL   = errors.New(`unexpected EOL`)
    30  )
    31  
    32  var eofToken = &token{tt: tokenEOF}
    33  
    34  type parserState func(*parser) parserState
    35  
    36  type parser struct {
    37  	tokens []token
    38  	length int
    39  	pos    int
    40  
    41  	words chan interface{}
    42  
    43  	root RootNode
    44  }
    45  
    46  func newParser(r io.Reader) (*parser, error) {
    47  	tokens, err := tokenize(r)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	p := &parser{
    53  		words:  make(chan interface{}),
    54  		tokens: tokens,
    55  		length: len(tokens),
    56  	}
    57  	return p, nil
    58  }
    59  
    60  func (p *parser) emit(message interface{}) {
    61  	p.words <- message
    62  }
    63  
    64  func (p *parser) collect() error {
    65  	for tok := range p.words {
    66  		switch word := tok.(type) {
    67  
    68  		case error:
    69  			return word
    70  
    71  		case Node:
    72  			p.root.Push(word)
    73  
    74  		case tokenType:
    75  			if tok == tokenEOF {
    76  				return nil
    77  			}
    78  
    79  		default:
    80  			log.Fatalf("unexpected: %v (%T)", word, word)
    81  		}
    82  	}
    83  
    84  	return nil
    85  }
    86  
    87  func (p *parser) run() error {
    88  	go func() {
    89  		state := parserDefaultState
    90  		for state != nil {
    91  			fname := runtime.FuncForPC(reflect.ValueOf(state).Pointer()).Name()
    92  			_ = fname
    93  			//log.Printf("state: %v", fname)
    94  			state = state(p)
    95  		}
    96  	}()
    97  
    98  	err := p.collect()
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  func (p *parser) continueUntilEOL() error {
   107  	for {
   108  		tok := p.cursor()
   109  
   110  		switch tok.tt {
   111  		case tokenNewLine, tokenEOF:
   112  			return nil
   113  		}
   114  
   115  		p.next()
   116  	}
   117  
   118  	panic("unreachable")
   119  }
   120  
   121  func (p *parser) expectOptionalCommentOrEOL() error {
   122  	for {
   123  		tok := p.cursor()
   124  
   125  		switch tok.tt {
   126  
   127  		case tokenWhitespace:
   128  			p.next()
   129  			continue
   130  
   131  		case tokenEOF, tokenNewLine:
   132  			// end of line
   133  			return nil
   134  
   135  		case tokenHash:
   136  			return p.continueUntilEOL()
   137  
   138  		default:
   139  			// another kind of token, stop
   140  			return errUnexpectedToken
   141  		}
   142  
   143  		panic("unreachable")
   144  	}
   145  
   146  	return nil
   147  }
   148  
   149  func (p *parser) stateError(err error) parserState {
   150  	cur := p.cursor()
   151  	err = fmt.Errorf("parse error: %q near %q (line: %d, col: %d)", err, cur.val, cur.line, cur.col)
   152  	p.emit(err)
   153  	return nil
   154  }
   155  
   156  func (p *parser) rewind(n int) bool {
   157  	if p.pos-n < 0 {
   158  		return false
   159  	}
   160  	p.pos = p.pos - n
   161  	return true
   162  }
   163  
   164  func (p *parser) next() bool {
   165  	if p.pos >= p.length {
   166  		return false
   167  	}
   168  	p.pos = p.pos + 1
   169  	return true
   170  }
   171  
   172  func (p *parser) cursor() *token {
   173  	if p.pos >= p.length {
   174  		return eofToken
   175  	}
   176  	return &p.tokens[p.pos]
   177  }
   178  
   179  func (p *parser) expectStringValue() (*token, error) {
   180  	tok := p.cursor()
   181  	p.next()
   182  
   183  	if tok.tt != tokenQuote {
   184  		return nil, errors.New("strings must start with a quote")
   185  	}
   186  
   187  	tokens := []*token{}
   188  
   189  loop:
   190  	for {
   191  		tok := p.cursor()
   192  		p.next()
   193  
   194  		switch tok.tt {
   195  
   196  		case tokenNewLine:
   197  			return nil, errUnexpectedEOL
   198  
   199  		case tokenEOF:
   200  			return nil, errUnexpectedEOF
   201  
   202  		case tokenBackslash:
   203  			// push backlash
   204  			tokens = append(tokens, tok)
   205  
   206  			// push whatever else comes next
   207  			tok = p.cursor()
   208  			p.next()
   209  
   210  		case tokenQuote:
   211  			// end of string
   212  			break loop
   213  		}
   214  
   215  		tokens = append(tokens, tok)
   216  	}
   217  
   218  	return composedValue(tokens)
   219  }
   220  
   221  func (p *parser) expectLiteralValue() (*token, error) {
   222  	tokens := []*token{}
   223  
   224  loop:
   225  	for {
   226  		// TODO: should we allow enclosing literal values with quotes?
   227  		tok := p.cursor()
   228  
   229  		switch tok.tt {
   230  
   231  		// literal value enclosed by string
   232  		case tokenQuote:
   233  			var err error
   234  			tok, err = p.expectStringValue()
   235  			if err != nil {
   236  				return nil, err
   237  			}
   238  			return tok, nil
   239  
   240  		case tokenWord:
   241  			tokens = append(tokens, tok)
   242  			p.next()
   243  
   244  		default:
   245  			break loop
   246  		}
   247  	}
   248  
   249  	return composedValue(tokens)
   250  }
   251  
   252  func (p *parser) match(tokenTypes ...tokenType) ([]*token, error) {
   253  	matcher := func(p *parser) ([]*token, error) {
   254  		tokens := make([]*token, 0, len(tokenTypes))
   255  		matches := 0
   256  
   257  		for _, expecting := range tokenTypes {
   258  			tok := p.cursor()
   259  
   260  			if tok.tt == expecting {
   261  				tokens = append(tokens, tok)
   262  				p.next()
   263  				matches++
   264  				continue
   265  			}
   266  
   267  			if expecting == tokenEOL {
   268  				err := p.expectOptionalCommentOrEOL()
   269  				if err != nil {
   270  					return nil, err
   271  				}
   272  				tokens = append(tokens, &token{
   273  					tt:   tokenEOL,
   274  					line: tok.line,
   275  					col:  tok.col,
   276  				})
   277  				break
   278  			}
   279  
   280  			if expecting != tokenWhitespace {
   281  				p.rewind(matches)
   282  				return nil, fmt.Errorf(`expecting %v, got %v`, expecting, tok)
   283  			}
   284  
   285  			tokens = append(tokens, &token{
   286  				tt:   tokenWhitespace,
   287  				val:  " ",
   288  				line: tok.line,
   289  				col:  tok.col,
   290  			})
   291  
   292  		}
   293  		return tokens, nil
   294  	}
   295  
   296  	return matcher(p)
   297  }
   298  
   299  func (p *parser) expectMetadataKey() (*token, error) {
   300  	matches, err := p.match(tokenWord)
   301  	if err != nil {
   302  		return nil, err
   303  	}
   304  
   305  	tokens := []*token{matches[0]}
   306  
   307  loop:
   308  	for {
   309  		tok := p.cursor()
   310  
   311  		switch tok.tt {
   312  		case tokenQuestionMark, tokenWhitespace, tokenEqual:
   313  			break loop
   314  		case tokenWord:
   315  			tokens = append(tokens, tok)
   316  		case tokenDot:
   317  			matches, err := p.match(tokenDot, tokenWord)
   318  			if err != nil {
   319  				return nil, err
   320  			}
   321  
   322  			tokens = append(tokens, matches...)
   323  			continue
   324  		default:
   325  			return nil, errUnexpectedToken
   326  		}
   327  
   328  		p.next()
   329  	}
   330  
   331  	return composedValue(tokens)
   332  }
   333  
   334  func (p *parser) expectMetadataValue() (*token, error) {
   335  	tok := p.cursor()
   336  
   337  	if tok.tt == tokenQuote {
   338  		var err error
   339  		tok, err = p.expectStringValue()
   340  		if err != nil {
   341  			return nil, err
   342  		}
   343  		return tok, nil
   344  	}
   345  
   346  	tokens := []*token{}
   347  loop:
   348  	for {
   349  		tok := p.cursor()
   350  
   351  		switch tok.tt {
   352  		case tokenWhitespace, tokenNewLine, tokenEOF:
   353  			break loop
   354  		}
   355  
   356  		tokens = append(tokens, tok)
   357  		p.next()
   358  	}
   359  
   360  	return composedValue(tokens)
   361  }
   362  
   363  func parserStateEOL(p *parser) parserState {
   364  	err := p.expectOptionalCommentOrEOL()
   365  	if err != nil {
   366  		return p.stateError(err)
   367  	}
   368  
   369  	return parserStateContinue
   370  }
   371  
   372  func parserStateComment(p *parser) parserState {
   373  	if err := p.continueUntilEOL(); err != nil {
   374  		return p.stateError(err)
   375  	}
   376  	return parserDefaultState
   377  }
   378  
   379  func parserStateDeclaration(p *parser) parserState {
   380  	word := p.cursor()
   381  	if word.tt != tokenWord {
   382  		return p.stateError(errUnexpectedToken)
   383  	}
   384  
   385  	switch word.val {
   386  	case wordWebRPC, wordName, wordVersion:
   387  		// <word> = <value> # optional comment
   388  		return parserStateDefinition
   389  	case wordImport:
   390  		// import
   391  		//   - <value> [<# comment>]
   392  		return parserStateImport
   393  	case wordEnum:
   394  		// enum <name>: <type>
   395  		//   - <name>[<space>=<space><value>][<#comment>]
   396  		return parserStateEnum
   397  	case wordMessage:
   398  		// message <name>
   399  		//   - <name>: <type>
   400  		//     + <tag.name> = <VALUE>
   401  		return parserStateMessage
   402  	case wordService:
   403  		// service <name>
   404  		//   - <name>([arguments]) [=> ([arguments])]
   405  		return parserStateService
   406  	default:
   407  		return p.stateError(errUnexpectedToken)
   408  	}
   409  
   410  	return parserDefaultState
   411  }
   412  
   413  func parserDefaultState(p *parser) parserState {
   414  	tok := p.cursor()
   415  
   416  	switch tok.tt {
   417  
   418  	case tokenWhitespace:
   419  		return parserStateSpace
   420  
   421  	case tokenNewLine:
   422  		return parserStateNewLine
   423  
   424  	case tokenWord:
   425  		return parserStateDeclaration
   426  
   427  	case tokenHash:
   428  		return parserStateComment
   429  
   430  	case tokenEOF:
   431  		return parserStateEOF
   432  
   433  	default:
   434  		return p.stateError(errUnexpectedToken)
   435  	}
   436  
   437  	return nil
   438  }
   439  
   440  func parserStateUnexpectedEOF(p *parser) parserState {
   441  	return p.stateError(errUnexpectedEOF)
   442  }
   443  
   444  func parserStateEOF(p *parser) parserState {
   445  	close(p.words)
   446  	return nil
   447  }
   448  
   449  func parserStateSpace(p *parser) parserState {
   450  	p.next()
   451  
   452  	for p.cursor().tt == tokenWhitespace {
   453  		// Keep skipping newlines.
   454  		p.next()
   455  	}
   456  
   457  	return parserDefaultState
   458  }
   459  
   460  func parserStateNewLine(p *parser) parserState {
   461  	p.next()
   462  
   463  	for p.cursor().tt == tokenNewLine {
   464  		// Keep skipping newlines.
   465  		p.next()
   466  	}
   467  
   468  	return parserDefaultState
   469  }
   470  
   471  func parserStateContinue(p *parser) parserState {
   472  	if !p.next() {
   473  		return parserStateEOF
   474  	}
   475  	return parserDefaultState
   476  }
   477  
   478  func composedValue(tokens []*token) (*token, error) {
   479  	if len(tokens) < 1 {
   480  		return nil, errors.New("expecting token")
   481  	}
   482  
   483  	baseToken := tokens[0]
   484  
   485  	values := []string{}
   486  	for _, t := range tokens {
   487  		values = append(values, t.val)
   488  	}
   489  
   490  	stringValue, err := unescapeString(strings.Join(values, ""))
   491  	if err != nil {
   492  		return nil, err
   493  	}
   494  
   495  	return &token{
   496  		tt:   tokenComposed,
   497  		val:  stringValue,
   498  		line: baseToken.line,
   499  		col:  baseToken.col,
   500  	}, nil
   501  }