From 8e4270e44a3afd8bf75e78382627827d7e267a04 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 28 Jan 2013 20:49:48 +0400 Subject: [PATCH] Fixed parsing ellipsis in slice lists (PY-8606) --- .../python/parsing/ExpressionParsing.java | 71 +++++++++---------- .../python/parsing/FunctionParsing.java | 8 +-- .../python/parsing/StatementParsing.java | 34 ++++----- python/testData/psi/EllipsisInSliceList.py | 1 + python/testData/psi/EllipsisInSliceList.txt | 21 ++++++ .../testData/psi/EllipsisInSliceListTail.py | 1 + .../testData/psi/EllipsisInSliceListTail.txt | 25 +++++++ .../jetbrains/python/PythonParsingTest.java | 10 +++ 8 files changed, 114 insertions(+), 57 deletions(-) create mode 100644 python/testData/psi/EllipsisInSliceList.py create mode 100644 python/testData/psi/EllipsisInSliceList.txt create mode 100644 python/testData/psi/EllipsisInSliceListTail.py create mode 100644 python/testData/psi/EllipsisInSliceListTail.txt diff --git a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java index 31681c8375e9..2de400cd57b6 100644 --- a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java +++ b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java @@ -97,7 +97,7 @@ public class ExpressionParsing extends Parsing { expr.done(PyElementTypes.LIST_LITERAL_EXPRESSION); return; } - if (!parseSingleExpression(isTargetExpression)) { + if (!parseSingleExpression(isTargetExpression, false)) { builder.error(message("PARSE.expected.expression")); } if (builder.getTokenType() == PyTokenTypes.FOR_KEYWORD) { @@ -111,7 +111,7 @@ public class ExpressionParsing extends Parsing { if (atToken(PyTokenTypes.RBRACKET)) { break; } - if (!parseSingleExpression(isTargetExpression)) { + if (!parseSingleExpression(isTargetExpression, false)) { builder.error(message("PARSE.expected.expr.or.comma.or.bracket")); break; } @@ -154,7 +154,7 @@ public class ExpressionParsing extends Parsing { result = parseORTestExpression(false, false); } else { - result = parseTupleExpression(false, false, true); + result = parseTupleExpression(false, false, true, false); } if (!result) { myBuilder.error("expression expected"); @@ -172,7 +172,7 @@ public class ExpressionParsing extends Parsing { } final PsiBuilder.Marker firstExprMarker = myBuilder.mark(); - if (!parseSingleExpression(false)) { + if (!parseSingleExpression(false, false)) { myBuilder.error("expression expected"); firstExprMarker.drop(); expr.done(PyElementTypes.DICT_LITERAL_EXPRESSION); @@ -198,7 +198,7 @@ public class ExpressionParsing extends Parsing { } private void parseDictLiteralTail(PsiBuilder.Marker startMarker, PsiBuilder.Marker firstKeyValueMarker) { - if (!parseSingleExpression(false)) { + if (!parseSingleExpression(false, false)) { myBuilder.error("expression expected"); firstKeyValueMarker.done(PyElementTypes.KEY_VALUE_EXPRESSION); if (atToken(PyTokenTypes.RBRACE)) { @@ -225,12 +225,12 @@ public class ExpressionParsing extends Parsing { private boolean parseKeyValueExpression() { final PsiBuilder.Marker marker = myBuilder.mark(); - if (!parseSingleExpression(false)) { + if (!parseSingleExpression(false, false)) { marker.drop(); return false; } checkMatches(PyTokenTypes.COLON, message("PARSE.expected.colon")); - if (!parseSingleExpression(false)) { + if (!parseSingleExpression(false, false)) { myBuilder.error("value expression expected"); marker.drop(); return false; @@ -242,7 +242,7 @@ public class ExpressionParsing extends Parsing { private void parseSetLiteralTail(PsiBuilder.Marker startMarker) { while (myBuilder.getTokenType() != PyTokenTypes.RBRACE) { checkMatches(PyTokenTypes.COMMA, message("PARSE.expected.comma")); - if (!parseSingleExpression(false)) { + if (!parseSingleExpression(false, false)) { break; } } @@ -325,9 +325,7 @@ public class ExpressionParsing extends Parsing { parseSliceEnd(expr, sliceItemStart); } else { - if (!parseEllipsis()) { - parseSingleExpression(false); - } + parseSingleExpression(false, true); if (atToken(PyTokenTypes.COLON)) { sliceOrTupleStart.drop(); parseSliceEnd(expr, sliceItemStart); @@ -336,7 +334,7 @@ public class ExpressionParsing extends Parsing { sliceItemStart.done(PyElementTypes.SLICE_ITEM); if (!parseSliceListTail(expr, sliceOrTupleStart)) { sliceOrTupleStart.rollbackTo(); - if (!parseTupleExpression(false, false, false)) { + if (!parseTupleExpression(false, false, false, true)) { myBuilder.error("tuple expression expected"); } checkMatches(PyTokenTypes.RBRACKET, message("PARSE.expected.rbracket")); @@ -403,13 +401,13 @@ public class ExpressionParsing extends Parsing { sliceMarker.done(PyElementTypes.EMPTY_EXPRESSION); } else { - parseSingleExpression(false); + parseSingleExpression(false, true); } if (!BRACKET_COLON_COMMA.contains(myBuilder.getTokenType())) { myBuilder.error(message("PARSE.expected.colon.or.rbracket")); } if (matchToken(PyTokenTypes.COLON)) { - parseSingleExpression(false); + parseSingleExpression(false, true); } sliceItemStart.done(PyElementTypes.SLICE_ITEM); @@ -426,14 +424,12 @@ public class ExpressionParsing extends Parsing { while (atToken(PyTokenTypes.COMMA)) { nextToken(); PsiBuilder.Marker sliceItemStart = myBuilder.mark(); - if (!parseEllipsis()) { - parseTestExpression(false, false); - } + parseTestExpression(false, false, true); if (matchToken(PyTokenTypes.COLON)) { inSlice = true; - parseTestExpression(false, false); + parseTestExpression(false, false, true); if (matchToken(PyTokenTypes.COLON)) { - parseTestExpression(false, false); + parseTestExpression(false, false, true); } } sliceItemStart.done(PyElementTypes.SLICE_ITEM); @@ -480,7 +476,7 @@ public class ExpressionParsing extends Parsing { if (myBuilder.getTokenType() == PyTokenTypes.MULT || myBuilder.getTokenType() == PyTokenTypes.EXP) { final PsiBuilder.Marker starArgMarker = myBuilder.mark(); myBuilder.advanceLexer(); - if (!parseSingleExpression(false)) { + if (!parseSingleExpression(false, false)) { myBuilder.error(message("PARSE.expected.expression")); } starArgMarker.done(PyElementTypes.STAR_ARGUMENT_EXPRESSION); @@ -491,7 +487,7 @@ public class ExpressionParsing extends Parsing { myBuilder.advanceLexer(); if (myBuilder.getTokenType() == PyTokenTypes.EQ) { myBuilder.advanceLexer(); - if (!parseSingleExpression(false)) { + if (!parseSingleExpression(false, false)) { myBuilder.error(message("PARSE.expected.expression")); } keywordArgMarker.done(PyElementTypes.KEYWORD_ARGUMENT_EXPRESSION); @@ -499,7 +495,7 @@ public class ExpressionParsing extends Parsing { } keywordArgMarker.rollbackTo(); } - if (!parseSingleExpression(false)) { + if (!parseSingleExpression(false, false)) { myBuilder.error(message("PARSE.expected.expression")); break; } @@ -514,11 +510,11 @@ public class ExpressionParsing extends Parsing { } public boolean parseExpressionOptional() { - return parseTupleExpression(false, false, false); + return parseTupleExpression(false, false, false, false); } public boolean parseExpressionOptional(boolean isTargetExpression) { - return parseTupleExpression(false, isTargetExpression, false); + return parseTupleExpression(false, isTargetExpression, false, false); } public void parseExpression() { @@ -528,7 +524,7 @@ public class ExpressionParsing extends Parsing { } public void parseExpression(boolean stopOnIn, boolean isTargetExpression) { - if (!parseTupleExpression(stopOnIn, isTargetExpression, false)) { + if (!parseTupleExpression(stopOnIn, isTargetExpression, false, false)) { myBuilder.error(message("PARSE.expected.expression")); } } @@ -539,7 +535,7 @@ public class ExpressionParsing extends Parsing { myBuilder.advanceLexer(); if (myBuilder.getTokenType() == PyTokenTypes.FROM_KEYWORD) { myBuilder.advanceLexer(); - final boolean parsed = parseTupleExpression(false, isTargetExpression, false); + final boolean parsed = parseTupleExpression(false, isTargetExpression, false, false); if (!parsed) { myBuilder.error(message("PARSE.expected.expression")); } @@ -547,19 +543,19 @@ public class ExpressionParsing extends Parsing { return parsed; } else { - parseTupleExpression(false, isTargetExpression, false); + parseTupleExpression(false, isTargetExpression, false, false); yieldExpr.done(PyElementTypes.YIELD_EXPRESSION); return true; } } else { - return parseTupleExpression(false, isTargetExpression, false); + return parseTupleExpression(false, isTargetExpression, false, false); } } - protected boolean parseTupleExpression(boolean stopOnIn, boolean isTargetExpression, final boolean oldTest) { + protected boolean parseTupleExpression(boolean stopOnIn, boolean isTargetExpression, final boolean oldTest, boolean allowEllipsis) { PsiBuilder.Marker expr = myBuilder.mark(); - boolean exprParseResult = oldTest ? parseOldTestExpression() : parseTestExpression(stopOnIn, isTargetExpression); + boolean exprParseResult = oldTest ? parseOldTestExpression() : parseTestExpression(stopOnIn, isTargetExpression, allowEllipsis); if (!exprParseResult) { expr.drop(); return false; @@ -568,7 +564,7 @@ public class ExpressionParsing extends Parsing { while (myBuilder.getTokenType() == PyTokenTypes.COMMA) { myBuilder.advanceLexer(); PsiBuilder.Marker expr2 = myBuilder.mark(); - exprParseResult = oldTest ? parseOldTestExpression() : parseTestExpression(stopOnIn, isTargetExpression); + exprParseResult = oldTest ? parseOldTestExpression() : parseTestExpression(stopOnIn, isTargetExpression, allowEllipsis); if (!exprParseResult) { expr2.rollbackTo(); break; @@ -583,8 +579,8 @@ public class ExpressionParsing extends Parsing { return true; } - public boolean parseSingleExpression(boolean isTargetExpression) { - return parseTestExpression(false, isTargetExpression); + public boolean parseSingleExpression(boolean isTargetExpression, boolean allowEllipsis) { + return parseTestExpression(false, isTargetExpression, allowEllipsis); } public boolean parseOldExpression() { @@ -594,10 +590,13 @@ public class ExpressionParsing extends Parsing { return parseORTestExpression(false, false); } - private boolean parseTestExpression(boolean stopOnIn, boolean isTargetExpression) { + private boolean parseTestExpression(boolean stopOnIn, boolean isTargetExpression, boolean allowEllipsis) { if (myBuilder.getTokenType() == PyTokenTypes.LAMBDA_KEYWORD) { return parseLambdaExpression(false); } + if (allowEllipsis && parseEllipsis()) { + return true; + } PsiBuilder.Marker condExpr = myBuilder.mark(); if (!parseORTestExpression( stopOnIn, isTargetExpression)) { condExpr.drop(); @@ -614,7 +613,7 @@ public class ExpressionParsing extends Parsing { } else { myBuilder.advanceLexer(); - if (!parseTestExpression(stopOnIn, isTargetExpression)) { + if (!parseTestExpression(stopOnIn, isTargetExpression, allowEllipsis)) { myBuilder.error(message("PARSE.expected.expression")); } } @@ -638,7 +637,7 @@ public class ExpressionParsing extends Parsing { PsiBuilder.Marker expr = myBuilder.mark(); myBuilder.advanceLexer(); getFunctionParser().parseParameterListContents(PyTokenTypes.COLON, false, true); - boolean parseExpressionResult = oldTest ? parseOldTestExpression() : parseSingleExpression(false); + boolean parseExpressionResult = oldTest ? parseOldTestExpression() : parseSingleExpression(false, false); if (!parseExpressionResult) { myBuilder.error(message("PARSE.expected.expression")); } diff --git a/python/src/com/jetbrains/python/parsing/FunctionParsing.java b/python/src/com/jetbrains/python/parsing/FunctionParsing.java index 7bb12f969585..40ca2371334d 100644 --- a/python/src/com/jetbrains/python/parsing/FunctionParsing.java +++ b/python/src/com/jetbrains/python/parsing/FunctionParsing.java @@ -65,7 +65,7 @@ public class FunctionParsing extends Parsing { PsiBuilder.Marker maybeReturnAnnotation = myBuilder.mark(); nextToken(); if (matchToken(PyTokenTypes.GT)) { - if (!myContext.getExpressionParser().parseSingleExpression(false)) { + if (!myContext.getExpressionParser().parseSingleExpression(false, false)) { myBuilder.error(message("PARSE.expected.expression")); } maybeReturnAnnotation.done(PyElementTypes.ANNOTATION); @@ -195,13 +195,13 @@ public class FunctionParsing extends Parsing { if (!isLambda && myContext.getLanguageLevel().isPy3K() && atToken(PyTokenTypes.COLON)) { PsiBuilder.Marker annotationMarker = myBuilder.mark(); nextToken(); - if (!getExpressionParser().parseSingleExpression(false)) { + if (!getExpressionParser().parseSingleExpression(false, false)) { myBuilder.error(message("PARSE.expected.expression")); } annotationMarker.done(PyElementTypes.ANNOTATION); } if (!isStarParameter && matchToken(PyTokenTypes.EQ)) { - if (!getExpressionParser().parseSingleExpression(false)) { + if (!getExpressionParser().parseSingleExpression(false, false)) { PsiBuilder.Marker invalidElements = myBuilder.mark(); while(!atAnyOfTokens(endToken, PyTokenTypes.LINE_BREAK, PyTokenTypes.COMMA, null)) { nextToken(); @@ -251,7 +251,7 @@ public class FunctionParsing extends Parsing { } if (myBuilder.getTokenType() == PyTokenTypes.EQ) { myBuilder.advanceLexer(); - getExpressionParser().parseSingleExpression(false); + getExpressionParser().parseSingleExpression(false, false); } tuple.done(PyElementTypes.TUPLE_PARAMETER); } diff --git a/python/src/com/jetbrains/python/parsing/StatementParsing.java b/python/src/com/jetbrains/python/parsing/StatementParsing.java index 51e35da65b7b..f07e7f8fbfab 100644 --- a/python/src/com/jetbrains/python/parsing/StatementParsing.java +++ b/python/src/com/jetbrains/python/parsing/StatementParsing.java @@ -273,18 +273,18 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { if (builder.getTokenType() == PyTokenTypes.GTGT) { final PsiBuilder.Marker target = builder.mark(); builder.advanceLexer(); - getExpressionParser().parseSingleExpression(false); + getExpressionParser().parseSingleExpression(false, false); target.done(PyElementTypes.PRINT_TARGET); } else { - getExpressionParser().parseSingleExpression(false); + getExpressionParser().parseSingleExpression(false, false); } while (builder.getTokenType() == PyTokenTypes.COMMA) { builder.advanceLexer(); if (getEndOfStatementsTokens().contains(builder.getTokenType())) { break; } - getExpressionParser().parseSingleExpression(false); + getExpressionParser().parseSingleExpression(false, false); } checkEndOfStatement(scope); statement.done(PyElementTypes.PRINT_STATEMENT); @@ -312,13 +312,13 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { assertCurrentToken(PyTokenTypes.DEL_KEYWORD); final PsiBuilder.Marker delStatement = myBuilder.mark(); myBuilder.advanceLexer(); - if (!getExpressionParser().parseSingleExpression(false)) { + if (!getExpressionParser().parseSingleExpression(false, false)) { myBuilder.error("Expression expected"); } while (myBuilder.getTokenType() == PyTokenTypes.COMMA) { myBuilder.advanceLexer(); if (!getEndOfStatementsTokens().contains(myBuilder.getTokenType())) { - if (!getExpressionParser().parseSingleExpression(false)) { + if (!getExpressionParser().parseSingleExpression(false, false)) { myBuilder.error("Expression expected"); } } @@ -333,18 +333,18 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { final PsiBuilder.Marker raiseStatement = myBuilder.mark(); myBuilder.advanceLexer(); if (!getEndOfStatementsTokens().contains(myBuilder.getTokenType())) { - getExpressionParser().parseSingleExpression(false); + getExpressionParser().parseSingleExpression(false, false); if (myBuilder.getTokenType() == PyTokenTypes.COMMA) { myBuilder.advanceLexer(); - getExpressionParser().parseSingleExpression(false); + getExpressionParser().parseSingleExpression(false, false); if (myBuilder.getTokenType() == PyTokenTypes.COMMA) { myBuilder.advanceLexer(); - getExpressionParser().parseSingleExpression(false); + getExpressionParser().parseSingleExpression(false, false); } } else if (myBuilder.getTokenType() == PyTokenTypes.FROM_KEYWORD) { myBuilder.advanceLexer(); - if (!getExpressionParser().parseSingleExpression(false)) { + if (!getExpressionParser().parseSingleExpression(false, false)) { myBuilder.error("Expression expected"); } } @@ -357,10 +357,10 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { assertCurrentToken(PyTokenTypes.ASSERT_KEYWORD); final PsiBuilder.Marker assertStatement = myBuilder.mark(); myBuilder.advanceLexer(); - if (getExpressionParser().parseSingleExpression(false)) { + if (getExpressionParser().parseSingleExpression(false, false)) { if (myBuilder.getTokenType() == PyTokenTypes.COMMA) { myBuilder.advanceLexer(); - if (!getExpressionParser().parseSingleExpression(false)) { + if (!getExpressionParser().parseSingleExpression(false, false)) { myContext.getBuilder().error(EXPRESSION_EXPECTED); } } @@ -557,10 +557,10 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { getExpressionParser().parseExpression(true, false); if (myBuilder.getTokenType() == PyTokenTypes.IN_KEYWORD) { myBuilder.advanceLexer(); - getExpressionParser().parseSingleExpression(false); + getExpressionParser().parseSingleExpression(false, false); if (myBuilder.getTokenType() == PyTokenTypes.COMMA) { myBuilder.advanceLexer(); - getExpressionParser().parseSingleExpression(false); + getExpressionParser().parseSingleExpression(false, false); } } checkEndOfStatement(inSuite); @@ -641,7 +641,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { final PsiBuilder.Marker statement = myBuilder.mark(); final PsiBuilder.Marker whilePart = myBuilder.mark(); myBuilder.advanceLexer(); - if (!getExpressionParser().parseSingleExpression(false)) { + if (!getExpressionParser().parseSingleExpression(false, false)) { myBuilder.error(EXPRESSION_EXPECTED); } parseColonAndSuite(scope); @@ -672,13 +672,13 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { final PsiBuilder.Marker exceptBlock = myBuilder.mark(); myBuilder.advanceLexer(); if (myBuilder.getTokenType() != PyTokenTypes.COLON) { - if (!getExpressionParser().parseSingleExpression(false)) { + if (!getExpressionParser().parseSingleExpression(false, false)) { myBuilder.error(EXPRESSION_EXPECTED); } myExpectAsKeyword = true; if (myBuilder.getTokenType() == PyTokenTypes.COMMA || myBuilder.getTokenType() == PyTokenTypes.AS_KEYWORD) { myBuilder.advanceLexer(); - if (!getExpressionParser().parseSingleExpression(true)) { + if (!getExpressionParser().parseSingleExpression(true, false)) { myBuilder.error(EXPRESSION_EXPECTED); } } @@ -733,7 +733,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { myExpectAsKeyword = true; if (myBuilder.getTokenType() == PyTokenTypes.AS_KEYWORD) { myBuilder.advanceLexer(); - getExpressionParser().parseSingleExpression(true); // 'as' is followed by a target + getExpressionParser().parseSingleExpression(true, false); // 'as' is followed by a target } withItem.done(PyElementTypes.WITH_ITEM); if (!matchToken(PyTokenTypes.COMMA)) { diff --git a/python/testData/psi/EllipsisInSliceList.py b/python/testData/psi/EllipsisInSliceList.py new file mode 100644 index 000000000000..2b9dee1ef3c9 --- /dev/null +++ b/python/testData/psi/EllipsisInSliceList.py @@ -0,0 +1 @@ +y = x[..., 0] diff --git a/python/testData/psi/EllipsisInSliceList.txt b/python/testData/psi/EllipsisInSliceList.txt new file mode 100644 index 000000000000..cf93afe255b6 --- /dev/null +++ b/python/testData/psi/EllipsisInSliceList.txt @@ -0,0 +1,21 @@ +PyFile:EllipsisInSliceList.py + PyAssignmentStatement + PyTargetExpression: y + PsiElement(Py:IDENTIFIER)('y') + PsiWhiteSpace(' ') + PsiElement(Py:EQ)('=') + PsiWhiteSpace(' ') + PySubscriptionExpression + PyReferenceExpression: x + PsiElement(Py:IDENTIFIER)('x') + PsiElement(Py:LBRACKET)('[') + PyTupleExpression + PyNoneLiteralExpression + PsiElement(Py:DOT)('.') + PsiElement(Py:DOT)('.') + PsiElement(Py:DOT)('.') + PsiElement(Py:COMMA)(',') + PsiWhiteSpace(' ') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('0') + PsiElement(Py:RBRACKET)(']') diff --git a/python/testData/psi/EllipsisInSliceListTail.py b/python/testData/psi/EllipsisInSliceListTail.py new file mode 100644 index 000000000000..ed5d55bef91a --- /dev/null +++ b/python/testData/psi/EllipsisInSliceListTail.py @@ -0,0 +1 @@ +y = x[0, ..., 2] diff --git a/python/testData/psi/EllipsisInSliceListTail.txt b/python/testData/psi/EllipsisInSliceListTail.txt new file mode 100644 index 000000000000..844ca2cb48d1 --- /dev/null +++ b/python/testData/psi/EllipsisInSliceListTail.txt @@ -0,0 +1,25 @@ +PyFile:EllipsisInSliceListTail.py + PyAssignmentStatement + PyTargetExpression: y + PsiElement(Py:IDENTIFIER)('y') + PsiWhiteSpace(' ') + PsiElement(Py:EQ)('=') + PsiWhiteSpace(' ') + PySubscriptionExpression + PyReferenceExpression: x + PsiElement(Py:IDENTIFIER)('x') + PsiElement(Py:LBRACKET)('[') + PyTupleExpression + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('0') + PsiElement(Py:COMMA)(',') + PsiWhiteSpace(' ') + PyNoneLiteralExpression + PsiElement(Py:DOT)('.') + PsiElement(Py:DOT)('.') + PsiElement(Py:DOT)('.') + PsiElement(Py:COMMA)(',') + PsiWhiteSpace(' ') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('2') + PsiElement(Py:RBRACKET)(']') diff --git a/python/testSrc/com/jetbrains/python/PythonParsingTest.java b/python/testSrc/com/jetbrains/python/PythonParsingTest.java index 283ea51cc1e4..2b6f511eb655 100644 --- a/python/testSrc/com/jetbrains/python/PythonParsingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonParsingTest.java @@ -360,6 +360,16 @@ public class PythonParsingTest extends ParsingTestCase { doTest(); } + // PY-8606 + public void testEllipsisInSliceList() { + doTest(); + } + + // PY-8606 + public void testEllipsisInSliceListTail() { + doTest(); + } + public void doTest(LanguageLevel languageLevel) { LanguageLevel prev = myLanguageLevel; myLanguageLevel = languageLevel;