github.com/k14s/starlark-go@v0.0.0-20200720175618-3a5c849cc368/syntax/block_scanner.go (about)

     1  package syntax
     2  
     3  import (
     4  	"fmt"
     5  )
     6  
     7  var _ = fmt.Sprintf
     8  
     9  type scannerInterface interface {
    10  	nextToken(*tokenValue) Token
    11  	error(pos Position, s string)
    12  	errorf(pos Position, format string, args ...interface{})
    13  	recover(*error)
    14  
    15  	getLineComments() []Comment
    16  	getSuffixComments() []Comment
    17  	getPos() Position
    18  }
    19  
    20  // blockScanner changes INDENT/OUTDENT to be
    21  // based on nesting depth (start->end) instead of whitespace
    22  type blockScanner struct {
    23  	scanner     *scanner
    24  	nextTokens  []blockScannerToken
    25  	prevTokens  []blockScannerToken
    26  	indentStack []blockScannerToken
    27  	debug       bool
    28  }
    29  
    30  var _ scannerInterface = &blockScanner{}
    31  
    32  type blockScannerToken struct {
    33  	val tokenValue
    34  	tok Token
    35  	alreadyOutdented bool
    36  }
    37  
    38  func newBlockScanner(s *scanner) *blockScanner {
    39  	return &blockScanner{s, nil, nil, nil, false}
    40  }
    41  
    42  func (s *blockScanner) nextToken(outVal *tokenValue) Token {
    43  	pair := s.nextTokenInner()
    44  	s.prevTokens = append(s.prevTokens, pair)
    45  
    46  	if s.debug {
    47  		fmt.Printf("emit: %s => %#v\n", pair.tok.String(), pair.val)
    48  	}
    49  
    50  	s.copyTokenValue(pair.val, outVal)
    51  	return pair.tok
    52  }
    53  
    54  func (s *blockScanner) nextTokenInner() blockScannerToken {
    55  	if s.matchesNewBlock() {
    56  		return s.buildIndent()
    57  	}
    58  
    59  	var currToken blockScannerToken
    60  	var tokSource string
    61  
    62  	if len(s.nextTokens) > 0 {
    63  		tokSource = "read-buffer"
    64  		currToken = s.nextTokens[0]
    65  		s.nextTokens = s.nextTokens[1:]
    66  	} else {
    67  		tokSource = "read"
    68  		currToken = s.popNextToken()
    69  	}
    70  
    71  	if s.debug {
    72  		fmt.Printf("\n%s: %s => %#v\n", tokSource, currToken.tok.String(), currToken.val)
    73  	}
    74  
    75  	switch currToken.tok {
    76  	// 'else' is a special cases when we need to
    77  	// implicitly outdent since end is not specified in code
    78  	case ELSE:
    79  		if !currToken.alreadyOutdented {
    80  			// if 'else' is not followed by the colon assume
    81  			// this is an inline if-else hence no need to outdent
    82  			maybeColonToken := s.popNextToken()
    83  
    84  			if maybeColonToken.tok == COLON {
    85  				currToken.alreadyOutdented = true
    86  				s.putBackToken(currToken)
    87  				s.putBackToken(maybeColonToken)
    88  				return s.buildOutdent()
    89  			}
    90  
    91  			s.putBackToken(maybeColonToken)
    92  		}
    93  
    94  	// 'elif' is special cases when we need to
    95  	// implicitly outdent since end is not specified in code
    96  	case ELIF:
    97  		if !currToken.alreadyOutdented {
    98  			currToken.alreadyOutdented = true
    99  			s.putBackToken(currToken)
   100  			return s.buildOutdent()
   101  		}
   102  
   103  	// Skip parsed indent/outdent as we insert
   104  	// our own "indention" at appropriate times
   105  	case INDENT, OUTDENT:
   106  		return s.nextTokenInner()
   107  
   108  	case PASS:
   109  		s.errorf(s.getPos(), "use of reserved keyword 'pass' is not allowed")
   110  
   111  	// 'end' is identifier
   112  	case IDENT:
   113  		if currToken.val.raw == "end" {
   114  			s.swallowNextToken(NEWLINE)
   115  			return s.buildOutdent()
   116  		}
   117  
   118  	case EOF:
   119  		if len(s.indentStack) != 0 {
   120  			pos := s.indentStack[len(s.indentStack)-1].val.pos
   121  			s.errorf(pos, "mismatched set of block openings (if/else/elif/for/def) and closing (end)")
   122  		}
   123  
   124  	default:
   125  		// continue with curr token
   126  	}
   127  
   128  	return currToken
   129  }
   130  
   131  func (s *blockScanner) popNextToken() blockScannerToken {
   132  	val := tokenValue{}
   133  	tok := s.scanner.nextToken(&val)
   134  	return blockScannerToken{tok: tok, val: val}
   135  }
   136  
   137  func (s *blockScanner) putBackToken(pair blockScannerToken) blockScannerToken {
   138  	s.nextTokens = append(s.nextTokens, pair)
   139  	return pair
   140  }
   141  
   142  func (s *blockScanner) swallowNextToken(tok Token) {
   143  	token := s.popNextToken()
   144  	if token.tok != tok {
   145  		s.putBackToken(token)
   146  	}
   147  }
   148  
   149  func (s *blockScanner) buildIndent() blockScannerToken {
   150  	s.indentStack = append(s.indentStack, s.prevTokens[len(s.prevTokens)-1])
   151  	return blockScannerToken{
   152  		tok: Token(INDENT),
   153  		val: tokenValue{pos: s.prevTokens[len(s.prevTokens)-1].val.pos},
   154  	}
   155  }
   156  
   157  func (s *blockScanner) buildOutdent() blockScannerToken {
   158  	if len(s.indentStack) == 0 {
   159  		s.error(s.getPos(), "unexpected end")
   160  	}
   161  	s.indentStack = s.indentStack[:len(s.indentStack)-1]
   162  	return blockScannerToken{
   163  		tok: Token(OUTDENT),
   164  		val: tokenValue{pos: s.prevTokens[len(s.prevTokens)-1].val.pos},
   165  	}
   166  }
   167  
   168  func (s *blockScanner) matchesNewBlock() bool {
   169  	if len(s.prevTokens) < 2 {
   170  		return false
   171  	}
   172  	lastLastColon := s.prevTokens[len(s.prevTokens)-2].tok == COLON
   173  	lastNewline := s.prevTokens[len(s.prevTokens)-1].tok == NEWLINE
   174  	return lastLastColon && lastNewline
   175  }
   176  
   177  func (s *blockScanner) copyTokenValue(left tokenValue, right *tokenValue) {
   178  	right.raw = left.raw
   179  	right.int = left.int
   180  	right.bigInt = left.bigInt
   181  	right.float = left.float
   182  	right.string = left.string
   183  	right.pos = left.pos
   184  }
   185  
   186  // implement boring scanner methods
   187  func (s *blockScanner) error(pos Position, str string) {
   188  	s.scanner.error(pos, str)
   189  }
   190  
   191  func (s *blockScanner) errorf(pos Position, format string, args ...interface{}) {
   192  	s.scanner.errorf(pos, format, args...)
   193  }
   194  
   195  func (s *blockScanner) recover(err *error) {
   196  	s.scanner.recover(err)
   197  }
   198  
   199  func (s *blockScanner) getLineComments() []Comment {
   200  	return s.scanner.getLineComments()
   201  }
   202  
   203  func (s *blockScanner) getSuffixComments() []Comment {
   204  	return s.scanner.getSuffixComments()
   205  }
   206  
   207  func (s *blockScanner) getPos() Position { return s.scanner.getPos() }
   208  
   209  // augment regular scanner
   210  var _ scannerInterface = &scanner{}
   211  
   212  func (s *scanner) getLineComments() []Comment { return s.lineComments }
   213  func (s *scanner) getSuffixComments() []Comment { return s.suffixComments }
   214  func (s *scanner) getPos() Position { return s.pos }