package org.python.pydev.parser; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.python.pydev.parser.fastparser.grammar_fstrings_common.FStringsAST; import org.python.pydev.parser.fastparser.grammar_fstrings_common.SimpleNode; import org.python.pydev.parser.grammar_fstrings.FStringsGrammar; import org.python.pydev.parser.jython.FastCharStream; import org.python.pydev.parser.jython.ParseException; import org.python.pydev.shared_core.string.StringUtils; import org.python.pydev.shared_core.structure.Tuple; import org.python.pydev.shared_core.utils.ArrayUtils; import junit.framework.TestCase; public class FStringsParserTest extends TestCase { private Tuple<FStringsAST, List> check(String str) throws ParseException { FastCharStream in = new FastCharStream(str.toCharArray()); FStringsGrammar fStringsGrammar = new FStringsGrammar(in); FStringsAST ast = fStringsGrammar.f_string(); //Note: we always try to generate a valid AST and get any errors in getParseErrors(). List<ParseException> parseErrors = fStringsGrammar.getParseErrors(); // System.out.println("\n\n-----\n" + str); // ast.dump(""); return new Tuple<>(ast, parseErrors); } private Tuple<FStringsAST, List> checkExprs(String str, Set<String> exprs) throws ParseException, BadLocationException { Tuple<FStringsAST, List> ret = check(str); // ret.o1.dump(); Document doc = new Document(str); Set<String> found = new HashSet<>(); for (SimpleNode b : ret.o1.getBalancedExpressions()) { String contents = b.getContentsFromString(str, doc); found.add(contents); } assertEquals(exprs, found); return ret; } private void checkError(String str, String... expected) throws ParseException { Tuple<FStringsAST, List> tup = check(str); if (tup.o2 == null || tup.o2.size() == 0) { fail("Expected error"); } for (String s : expected) { boolean found = false; for (Object o : tup.o2) { if (o.toString().contains(s)) { found = true; } } if (!found) { for (Object o : tup.o2) { ((Throwable) o).printStackTrace(); } fail("Expected error message with: " + s + "\nAvailable:\n" + StringUtils.join("\n", tup.o2)); } } } public void testFStringParsing() throws ParseException, BadLocationException { checkExprs("{{'c':20}}", ArrayUtils.asSet("{'c':20}")); checkExprs("a{text}a{text2}b", ArrayUtils.asSet("text", "text2")); checkExprs("{text!a}", ArrayUtils.asSet("text")); checkExprs("{text!s}", ArrayUtils.asSet("text")); checkExprs("{text!r}", ArrayUtils.asSet("text")); checkExprs("{text!r:foo}", ArrayUtils.asSet("text")); checkExprs("{text:#.6f}", ArrayUtils.asSet("text")); checkExprs("{text#.6f}", ArrayUtils.asSet("text#.6f")); checkExprs("{text:}", ArrayUtils.asSet("text")); checkExprs("newline: {call('{}')}", ArrayUtils.asSet("call('{}')")); checkExprs("'.nhunsoeth{'{uoesnth{ueo:{}''}'}", ArrayUtils.asSet("'{uoesnth{ueo:{}''}'")); checkExprs("\".nhunsoeth{'{uoesnth{ueo:{}''}'}", ArrayUtils.asSet("'{uoesnth{ueo:{}''}'")); checkExprs("{text:}", ArrayUtils.asSet("text")); checkExprs("{call(\"\")}", ArrayUtils.asSet("call(\"\")")); checkExprs("slash\\\\{aa}", ArrayUtils.asSet("aa")); checkExprs("special chars !\\:'\"()[]{aa}", ArrayUtils.asSet("aa")); checkExprs("multi\nline\n{aaa}", ArrayUtils.asSet("aaa")); checkError("{}", "Empty expression not allowed in f-string"); checkError("{ }", "Empty expression not allowed in f-string"); checkError("{!}", "Empty expression not allowed in f-string", "Only '!a', '!s' or '!r' accepted."); checkError("{!a}", "Empty expression not allowed in f-string"); checkError("{:}", "Empty expression not allowed in f-string"); checkError("{text!}", "Only '!a', '!s' or '!r' accepted."); checkError("}", "Single '}' not allowed"); checkError("{text", "Unbalanced '{'"); checkError("{\"a}", "Unbalanced '\"'"); checkError("{ { }", "Unbalanced '{'"); checkError("{ ( }", "Unbalanced '('"); checkError("{ [ }", "Unbalanced '['"); checkError("{'a}", "Unbalanced \"'\""); checkError("{text!x}", "Expecting '!a', '!s' or '!r'. Found: x"); checkError("{no backslash\\\\n}", "Backslash (\\) not valid inside f-string expressions."); checkError("{no backslash'\\\\n'}", "Backslash (\\) not valid inside f-string expressions."); } }