diff --git a/grammars/MagicPython.cson b/grammars/MagicPython.cson index 6486dba4..0e32aa04 100644 --- a/grammars/MagicPython.cson +++ b/grammars/MagicPython.cson @@ -294,6 +294,23 @@ repository: ''' } + { + name: "keyword.control.flow.python" + comment: ''' + `match` and `case` are only soft keywords. Match if + followed by `:` or anything that allows for line continuation. + + ''' + match: ''' + (?x) + (?:^\\s*)\\b( + match | case + )\\b(?: + (?=.*[:\\\\]) | (?=\\s*[\\(\\[\\{]) + ) + + ''' + } { name: "storage.modifier.declaration.python" match: ''' diff --git a/grammars/MagicPython.tmLanguage b/grammars/MagicPython.tmLanguage index 5a07f545..a8ddad8e 100644 --- a/grammars/MagicPython.tmLanguage +++ b/grammars/MagicPython.tmLanguage @@ -453,6 +453,22 @@ it's probably control flow like: | from | elif | else | if | except | pass | raise | return | try | while | with )\b + + + + name + keyword.control.flow.python + comment + `match` and `case` are only soft keywords. Match if +followed by `:` or anything that allows for line continuation. + + match + (?x) + (?:^\s*)\b( + match | case + )\b(?: + (?=.*[:\\]) | (?=\s*[\(\[\{]) + ) diff --git a/grammars/src/MagicPython.syntax.yaml b/grammars/src/MagicPython.syntax.yaml index 9c80c8b3..a8596b53 100644 --- a/grammars/src/MagicPython.syntax.yaml +++ b/grammars/src/MagicPython.syntax.yaml @@ -381,6 +381,17 @@ repository: | from | elif | else | if | except | pass | raise | return | try | while | with )\b + - name: keyword.control.flow.python + comment: | + `match` and `case` are only soft keywords. Match if + followed by `:` or anything that allows for line continuation. + match: | + (?x) + (?:^\s*)\b( + match | case + )\b(?: + (?=.*[:\\]) | (?=\s*[\(\[\{]) + ) - name: storage.modifier.declaration.python match: | (?x) diff --git a/test/atom-spec/python-spec.js b/test/atom-spec/python-spec.js index 032c3682..d268f730 100644 --- a/test/atom-spec/python-spec.js +++ b/test/atom-spec/python-spec.js @@ -13020,6 +13020,214 @@ describe("Grammar Tests", function() { expect(tokens[8][11].scopes).toEqual(["source.python","constant.language.python"]); }); + it("test/statements/match1.py", + function() { + tokens = grammar.tokenizeLines("match status:\n case 100:\n pass\n case \\\n 200:\n pass\n case (\n 300\n ):\n pass\n case [\n 400\n ]:\n pass\n case {\n 500: \"\",\n }:\n pass\n case Point(x, y) as p:\n pass\n case 600 | 700:\n pass\n case _:") + expect(tokens[0][0].value).toBe("match"); + expect(tokens[0][0].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[0][1].value).toBe(" "); + expect(tokens[0][1].scopes).toEqual(["source.python"]); + expect(tokens[0][2].value).toBe("status"); + expect(tokens[0][2].scopes).toEqual(["source.python"]); + expect(tokens[0][3].value).toBe(":"); + expect(tokens[0][3].scopes).toEqual(["source.python","punctuation.separator.colon.python"]); + expect(tokens[1][0].value).toBe(" case"); + expect(tokens[1][0].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[1][1].value).toBe(" "); + expect(tokens[1][1].scopes).toEqual(["source.python"]); + expect(tokens[1][2].value).toBe("100"); + expect(tokens[1][2].scopes).toEqual(["source.python","constant.numeric.dec.python"]); + expect(tokens[1][3].value).toBe(":"); + expect(tokens[1][3].scopes).toEqual(["source.python","punctuation.separator.colon.python"]); + expect(tokens[2][0].value).toBe(" "); + expect(tokens[2][0].scopes).toEqual(["source.python"]); + expect(tokens[2][1].value).toBe("pass"); + expect(tokens[2][1].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[3][0].value).toBe(" case"); + expect(tokens[3][0].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[3][1].value).toBe(" "); + expect(tokens[3][1].scopes).toEqual(["source.python"]); + expect(tokens[3][2].value).toBe("\\"); + expect(tokens[3][2].scopes).toEqual(["source.python","punctuation.separator.continuation.line.python"]); + expect(tokens[3][3].value).toBe(""); + expect(tokens[3][3].scopes).toEqual(["source.python"]); + expect(tokens[4][0].value).toBe(" "); + expect(tokens[4][0].scopes).toEqual(["source.python"]); + expect(tokens[4][1].value).toBe("200"); + expect(tokens[4][1].scopes).toEqual(["source.python","constant.numeric.dec.python"]); + expect(tokens[4][2].value).toBe(":"); + expect(tokens[4][2].scopes).toEqual(["source.python","punctuation.separator.colon.python"]); + expect(tokens[5][0].value).toBe(" "); + expect(tokens[5][0].scopes).toEqual(["source.python"]); + expect(tokens[5][1].value).toBe("pass"); + expect(tokens[5][1].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[6][0].value).toBe(" case"); + expect(tokens[6][0].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[6][1].value).toBe(" "); + expect(tokens[6][1].scopes).toEqual(["source.python"]); + expect(tokens[6][2].value).toBe("("); + expect(tokens[6][2].scopes).toEqual(["source.python","punctuation.parenthesis.begin.python"]); + expect(tokens[7][0].value).toBe(" "); + expect(tokens[7][0].scopes).toEqual(["source.python"]); + expect(tokens[7][1].value).toBe("300"); + expect(tokens[7][1].scopes).toEqual(["source.python","constant.numeric.dec.python"]); + expect(tokens[8][0].value).toBe(" "); + expect(tokens[8][0].scopes).toEqual(["source.python"]); + expect(tokens[8][1].value).toBe(")"); + expect(tokens[8][1].scopes).toEqual(["source.python","punctuation.parenthesis.end.python"]); + expect(tokens[8][2].value).toBe(":"); + expect(tokens[8][2].scopes).toEqual(["source.python","punctuation.separator.colon.python"]); + expect(tokens[9][0].value).toBe(" "); + expect(tokens[9][0].scopes).toEqual(["source.python"]); + expect(tokens[9][1].value).toBe("pass"); + expect(tokens[9][1].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[10][0].value).toBe(" case"); + expect(tokens[10][0].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[10][1].value).toBe(" "); + expect(tokens[10][1].scopes).toEqual(["source.python"]); + expect(tokens[10][2].value).toBe("["); + expect(tokens[10][2].scopes).toEqual(["source.python","punctuation.definition.list.begin.python"]); + expect(tokens[11][0].value).toBe(" "); + expect(tokens[11][0].scopes).toEqual(["source.python"]); + expect(tokens[11][1].value).toBe("400"); + expect(tokens[11][1].scopes).toEqual(["source.python","constant.numeric.dec.python"]); + expect(tokens[12][0].value).toBe(" "); + expect(tokens[12][0].scopes).toEqual(["source.python"]); + expect(tokens[12][1].value).toBe("]"); + expect(tokens[12][1].scopes).toEqual(["source.python","punctuation.definition.list.end.python"]); + expect(tokens[12][2].value).toBe(":"); + expect(tokens[12][2].scopes).toEqual(["source.python","punctuation.separator.colon.python"]); + expect(tokens[13][0].value).toBe(" "); + expect(tokens[13][0].scopes).toEqual(["source.python"]); + expect(tokens[13][1].value).toBe("pass"); + expect(tokens[13][1].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[14][0].value).toBe(" case"); + expect(tokens[14][0].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[14][1].value).toBe(" "); + expect(tokens[14][1].scopes).toEqual(["source.python"]); + expect(tokens[14][2].value).toBe("{"); + expect(tokens[14][2].scopes).toEqual(["source.python","punctuation.definition.dict.begin.python"]); + expect(tokens[15][0].value).toBe(" "); + expect(tokens[15][0].scopes).toEqual(["source.python"]); + expect(tokens[15][1].value).toBe("500"); + expect(tokens[15][1].scopes).toEqual(["source.python","constant.numeric.dec.python"]); + expect(tokens[15][2].value).toBe(":"); + expect(tokens[15][2].scopes).toEqual(["source.python","punctuation.separator.dict.python"]); + expect(tokens[15][3].value).toBe(" "); + expect(tokens[15][3].scopes).toEqual(["source.python"]); + expect(tokens[15][4].value).toBe("\""); + expect(tokens[15][4].scopes).toEqual(["source.python","string.quoted.single.python","punctuation.definition.string.begin.python"]); + expect(tokens[15][5].value).toBe("\""); + expect(tokens[15][5].scopes).toEqual(["source.python","string.quoted.single.python","punctuation.definition.string.end.python"]); + expect(tokens[15][6].value).toBe(","); + expect(tokens[15][6].scopes).toEqual(["source.python","punctuation.separator.element.python"]); + expect(tokens[16][0].value).toBe(" "); + expect(tokens[16][0].scopes).toEqual(["source.python"]); + expect(tokens[16][1].value).toBe("}"); + expect(tokens[16][1].scopes).toEqual(["source.python","punctuation.definition.dict.end.python"]); + expect(tokens[16][2].value).toBe(":"); + expect(tokens[16][2].scopes).toEqual(["source.python","punctuation.separator.colon.python"]); + expect(tokens[17][0].value).toBe(" "); + expect(tokens[17][0].scopes).toEqual(["source.python"]); + expect(tokens[17][1].value).toBe("pass"); + expect(tokens[17][1].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[18][0].value).toBe(" case"); + expect(tokens[18][0].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[18][1].value).toBe(" "); + expect(tokens[18][1].scopes).toEqual(["source.python"]); + expect(tokens[18][2].value).toBe("Point"); + expect(tokens[18][2].scopes).toEqual(["source.python","meta.function-call.python","meta.function-call.generic.python"]); + expect(tokens[18][3].value).toBe("("); + expect(tokens[18][3].scopes).toEqual(["source.python","meta.function-call.python","punctuation.definition.arguments.begin.python"]); + expect(tokens[18][4].value).toBe("x"); + expect(tokens[18][4].scopes).toEqual(["source.python","meta.function-call.python","meta.function-call.arguments.python"]); + expect(tokens[18][5].value).toBe(","); + expect(tokens[18][5].scopes).toEqual(["source.python","meta.function-call.python","meta.function-call.arguments.python","punctuation.separator.arguments.python"]); + expect(tokens[18][6].value).toBe(" "); + expect(tokens[18][6].scopes).toEqual(["source.python","meta.function-call.python","meta.function-call.arguments.python"]); + expect(tokens[18][7].value).toBe("y"); + expect(tokens[18][7].scopes).toEqual(["source.python","meta.function-call.python","meta.function-call.arguments.python"]); + expect(tokens[18][8].value).toBe(")"); + expect(tokens[18][8].scopes).toEqual(["source.python","meta.function-call.python","punctuation.definition.arguments.end.python"]); + expect(tokens[18][9].value).toBe(" "); + expect(tokens[18][9].scopes).toEqual(["source.python"]); + expect(tokens[18][10].value).toBe("as"); + expect(tokens[18][10].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[18][11].value).toBe(" "); + expect(tokens[18][11].scopes).toEqual(["source.python"]); + expect(tokens[18][12].value).toBe("p"); + expect(tokens[18][12].scopes).toEqual(["source.python"]); + expect(tokens[18][13].value).toBe(":"); + expect(tokens[18][13].scopes).toEqual(["source.python","punctuation.separator.colon.python"]); + expect(tokens[19][0].value).toBe(" "); + expect(tokens[19][0].scopes).toEqual(["source.python"]); + expect(tokens[19][1].value).toBe("pass"); + expect(tokens[19][1].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[20][0].value).toBe(" case"); + expect(tokens[20][0].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[20][1].value).toBe(" "); + expect(tokens[20][1].scopes).toEqual(["source.python"]); + expect(tokens[20][2].value).toBe("600"); + expect(tokens[20][2].scopes).toEqual(["source.python","constant.numeric.dec.python"]); + expect(tokens[20][3].value).toBe(" "); + expect(tokens[20][3].scopes).toEqual(["source.python"]); + expect(tokens[20][4].value).toBe("|"); + expect(tokens[20][4].scopes).toEqual(["source.python","keyword.operator.bitwise.python"]); + expect(tokens[20][5].value).toBe(" "); + expect(tokens[20][5].scopes).toEqual(["source.python"]); + expect(tokens[20][6].value).toBe("700"); + expect(tokens[20][6].scopes).toEqual(["source.python","constant.numeric.dec.python"]); + expect(tokens[20][7].value).toBe(":"); + expect(tokens[20][7].scopes).toEqual(["source.python","punctuation.separator.colon.python"]); + expect(tokens[21][0].value).toBe(" "); + expect(tokens[21][0].scopes).toEqual(["source.python"]); + expect(tokens[21][1].value).toBe("pass"); + expect(tokens[21][1].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[22][0].value).toBe(" case"); + expect(tokens[22][0].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[22][1].value).toBe(" "); + expect(tokens[22][1].scopes).toEqual(["source.python"]); + expect(tokens[22][2].value).toBe("_"); + expect(tokens[22][2].scopes).toEqual(["source.python"]); + expect(tokens[22][3].value).toBe(":"); + expect(tokens[22][3].scopes).toEqual(["source.python","punctuation.separator.colon.python"]); + }); + + it("test/statements/match2.py", + function() { + tokens = grammar.tokenizeLines("match = 1\nif match == 2:\n pass") + expect(tokens[0][0].value).toBe("match"); + expect(tokens[0][0].scopes).toEqual(["source.python"]); + expect(tokens[0][1].value).toBe(" "); + expect(tokens[0][1].scopes).toEqual(["source.python"]); + expect(tokens[0][2].value).toBe("="); + expect(tokens[0][2].scopes).toEqual(["source.python","keyword.operator.assignment.python"]); + expect(tokens[0][3].value).toBe(" "); + expect(tokens[0][3].scopes).toEqual(["source.python"]); + expect(tokens[0][4].value).toBe("1"); + expect(tokens[0][4].scopes).toEqual(["source.python","constant.numeric.dec.python"]); + expect(tokens[1][0].value).toBe("if"); + expect(tokens[1][0].scopes).toEqual(["source.python","keyword.control.flow.python"]); + expect(tokens[1][1].value).toBe(" "); + expect(tokens[1][1].scopes).toEqual(["source.python"]); + expect(tokens[1][2].value).toBe("match"); + expect(tokens[1][2].scopes).toEqual(["source.python"]); + expect(tokens[1][3].value).toBe(" "); + expect(tokens[1][3].scopes).toEqual(["source.python"]); + expect(tokens[1][4].value).toBe("=="); + expect(tokens[1][4].scopes).toEqual(["source.python","keyword.operator.comparison.python"]); + expect(tokens[1][5].value).toBe(" "); + expect(tokens[1][5].scopes).toEqual(["source.python"]); + expect(tokens[1][6].value).toBe("2"); + expect(tokens[1][6].scopes).toEqual(["source.python","constant.numeric.dec.python"]); + expect(tokens[1][7].value).toBe(":"); + expect(tokens[1][7].scopes).toEqual(["source.python","punctuation.separator.colon.python"]); + expect(tokens[2][0].value).toBe(" "); + expect(tokens[2][0].scopes).toEqual(["source.python"]); + expect(tokens[2][1].value).toBe("pass"); + expect(tokens[2][1].scopes).toEqual(["source.python","keyword.control.flow.python"]); + }); + it("test/statements/nonlocal1.py", function() { tokens = grammar.tokenizeLines("nonlocal a, b, c") diff --git a/test/statements/match1.py b/test/statements/match1.py new file mode 100644 index 00000000..e0cebf7b --- /dev/null +++ b/test/statements/match1.py @@ -0,0 +1,110 @@ +match status: + case 100: + pass + case \ + 200: + pass + case ( + 300 + ): + pass + case [ + 400 + ]: + pass + case { + 500: "", + }: + pass + case Point(x, y) as p: + pass + case 600 | 700: + pass + case _: + + + +match : keyword.control.flow.python, source.python + : source.python +status : source.python +: : punctuation.separator.colon.python, source.python + case : keyword.control.flow.python, source.python + : source.python +100 : constant.numeric.dec.python, source.python +: : punctuation.separator.colon.python, source.python + : source.python +pass : keyword.control.flow.python, source.python + case : keyword.control.flow.python, source.python + : source.python +\ : punctuation.separator.continuation.line.python, source.python + : source.python + : source.python +200 : constant.numeric.dec.python, source.python +: : punctuation.separator.colon.python, source.python + : source.python +pass : keyword.control.flow.python, source.python + case : keyword.control.flow.python, source.python + : source.python +( : punctuation.parenthesis.begin.python, source.python + : source.python +300 : constant.numeric.dec.python, source.python + : source.python +) : punctuation.parenthesis.end.python, source.python +: : punctuation.separator.colon.python, source.python + : source.python +pass : keyword.control.flow.python, source.python + case : keyword.control.flow.python, source.python + : source.python +[ : punctuation.definition.list.begin.python, source.python + : source.python +400 : constant.numeric.dec.python, source.python + : source.python +] : punctuation.definition.list.end.python, source.python +: : punctuation.separator.colon.python, source.python + : source.python +pass : keyword.control.flow.python, source.python + case : keyword.control.flow.python, source.python + : source.python +{ : punctuation.definition.dict.begin.python, source.python + : source.python +500 : constant.numeric.dec.python, source.python +: : punctuation.separator.dict.python, source.python + : source.python +" : punctuation.definition.string.begin.python, source.python, string.quoted.single.python +" : punctuation.definition.string.end.python, source.python, string.quoted.single.python +, : punctuation.separator.element.python, source.python + : source.python +} : punctuation.definition.dict.end.python, source.python +: : punctuation.separator.colon.python, source.python + : source.python +pass : keyword.control.flow.python, source.python + case : keyword.control.flow.python, source.python + : source.python +Point : meta.function-call.generic.python, meta.function-call.python, source.python +( : meta.function-call.python, punctuation.definition.arguments.begin.python, source.python +x : meta.function-call.arguments.python, meta.function-call.python, source.python +, : meta.function-call.arguments.python, meta.function-call.python, punctuation.separator.arguments.python, source.python + : meta.function-call.arguments.python, meta.function-call.python, source.python +y : meta.function-call.arguments.python, meta.function-call.python, source.python +) : meta.function-call.python, punctuation.definition.arguments.end.python, source.python + : source.python +as : keyword.control.flow.python, source.python + : source.python +p : source.python +: : punctuation.separator.colon.python, source.python + : source.python +pass : keyword.control.flow.python, source.python + case : keyword.control.flow.python, source.python + : source.python +600 : constant.numeric.dec.python, source.python + : source.python +| : keyword.operator.bitwise.python, source.python + : source.python +700 : constant.numeric.dec.python, source.python +: : punctuation.separator.colon.python, source.python + : source.python +pass : keyword.control.flow.python, source.python + case : keyword.control.flow.python, source.python + : source.python +_ : source.python +: : punctuation.separator.colon.python, source.python diff --git a/test/statements/match2.py b/test/statements/match2.py new file mode 100644 index 00000000..5f600910 --- /dev/null +++ b/test/statements/match2.py @@ -0,0 +1,21 @@ +match = 1 +if match == 2: + pass + + + +match : source.python + : source.python += : keyword.operator.assignment.python, source.python + : source.python +1 : constant.numeric.dec.python, source.python +if : keyword.control.flow.python, source.python + : source.python +match : source.python + : source.python +== : keyword.operator.comparison.python, source.python + : source.python +2 : constant.numeric.dec.python, source.python +: : punctuation.separator.colon.python, source.python + : source.python +pass : keyword.control.flow.python, source.python