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 }