|
62 | 62 | var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/; |
63 | 63 | myKeywords = myKeywords.concat(["nonlocal", "False", "True", "None", "async", "await"]); |
64 | 64 | myBuiltins = myBuiltins.concat(["ascii", "bytes", "exec", "print"]); |
65 | | - var stringPrefixes = new RegExp("^(([rbuf]|(br))?('{3}|\"{3}|['\"]))", "i"); |
| 65 | + var stringPrefixes = new RegExp("^(([rbuf]|(br)|(fr))?('{3}|\"{3}|['\"]))", "i"); |
66 | 66 | } else { |
67 | 67 | var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*/; |
68 | 68 | myKeywords = myKeywords.concat(["exec", "print"]); |
|
142 | 142 |
|
143 | 143 | // Handle Strings |
144 | 144 | if (stream.match(stringPrefixes)) { |
145 | | - state.tokenize = tokenStringFactory(stream.current()); |
146 | | - return state.tokenize(stream, state); |
| 145 | + var isFmtString = stream.current().toLowerCase().indexOf('f') !== -1; |
| 146 | + if (!isFmtString || state.fstr_state !== null) { |
| 147 | + // if this is a nested format string (e.g. f' { f"{10*10}" + "a" }' ) |
| 148 | + // we do not format the nested expression and treat the nested format |
| 149 | + // string as regular string |
| 150 | + state.tokenize = tokenStringFactory(stream.current()); |
| 151 | + return state.tokenize(stream, state); |
| 152 | + } else { |
| 153 | + // need to do something more sophisticated |
| 154 | + state.tokenize = formatStringFactory(stream.current()); |
| 155 | + return state.tokenize(stream, state); |
| 156 | + } |
147 | 157 | } |
148 | 158 |
|
149 | 159 | for (var i = 0; i < operators.length; i++) |
|
174 | 184 | return ERRORCLASS; |
175 | 185 | } |
176 | 186 |
|
| 187 | + function formatStringFactory(delimiter) { |
| 188 | + while ("rubf".indexOf(delimiter.charAt(0).toLowerCase()) >= 0) |
| 189 | + delimiter = delimiter.substr(1); |
| 190 | + |
| 191 | + var singleline = delimiter.length == 1; |
| 192 | + var OUTCLASS = "string"; |
| 193 | + |
| 194 | + function tokenString(stream, state) { |
| 195 | + if (state.fstr_state) { |
| 196 | + // inside f-str Expression |
| 197 | + if (stream.match(delimiter)) { |
| 198 | + // expression ends pre-maturally, but very common in editing |
| 199 | + // Could show error to remind users to close brace here |
| 200 | + state.fstr_state = null; |
| 201 | + return OUTCLASS; |
| 202 | + } else if (stream.match('{')) { |
| 203 | + // starting brace, if not eaten below |
| 204 | + return "punctuation"; |
| 205 | + } else if (stream.match('}')) { |
| 206 | + // return to regular inside string state |
| 207 | + state.fstr_state = null; |
| 208 | + return "punctuation"; |
| 209 | + } else { |
| 210 | + // use tokenBaseInner to parse the expression |
| 211 | + return tokenBaseInner(stream, state.fstr_state); |
| 212 | + } |
| 213 | + } |
| 214 | + while (!stream.eol()) { |
| 215 | + stream.eatWhile(/[^'"\{\}\\]/); |
| 216 | + if (stream.eat("\\")) { |
| 217 | + stream.next(); |
| 218 | + if (singleline && stream.eol()) |
| 219 | + return OUTCLASS; |
| 220 | + } else if (stream.match(delimiter)) { |
| 221 | + state.tokenize = tokenBase; |
| 222 | + return OUTCLASS; |
| 223 | + } else if (stream.match('{{')) { |
| 224 | + // ignore {{ in f-str |
| 225 | + return OUTCLASS; |
| 226 | + } else if (stream.match('{', false)) { |
| 227 | + // switch to nested mode |
| 228 | + state.fstr_state = {}; |
| 229 | + if (stream.current()) { |
| 230 | + return OUTCLASS; |
| 231 | + } else { |
| 232 | + // need to return something, so eat the starting { |
| 233 | + stream.next(); |
| 234 | + return "punctuation"; |
| 235 | + } |
| 236 | + } else if (stream.match('}}')) { |
| 237 | + return OUTCLASS; |
| 238 | + } else if (stream.match('}')) { |
| 239 | + // single } in f-string is an error |
| 240 | + return ERRORCLASS; |
| 241 | + } else { |
| 242 | + stream.eat(/['"]/); |
| 243 | + } |
| 244 | + } |
| 245 | + if (singleline) { |
| 246 | + if (parserConf.singleLineStringErrors) |
| 247 | + return ERRORCLASS; |
| 248 | + else |
| 249 | + state.tokenize = tokenBase; |
| 250 | + } |
| 251 | + return OUTCLASS; |
| 252 | + } |
| 253 | + tokenString.isString = true; |
| 254 | + return tokenString; |
| 255 | + } |
| 256 | + |
177 | 257 | function tokenStringFactory(delimiter) { |
178 | 258 | while ("rubf".indexOf(delimiter.charAt(0).toLowerCase()) >= 0) |
179 | 259 | delimiter = delimiter.substr(1); |
|
278 | 358 | return { |
279 | 359 | tokenize: tokenBase, |
280 | 360 | scopes: [{offset: basecolumn || 0, type: "py", align: null}], |
| 361 | + fstr_state: null, |
281 | 362 | indent: basecolumn || 0, |
282 | 363 | lastToken: null, |
283 | 364 | lambda: false, |
|
0 commit comments