{
  "id": "P024",
  "slug": "bracket-checker",
  "key": "P024-bracket-checker",
  "title": "Bracket checker",
  "summary": "Checks the round, square and curly brackets of a typed text of up to 20 lines: the first error with its line, column and reason - a bracket that closes nothing, one of the wrong kind, or one never closed - the deepest nesting, and an outline of the text broken at its brackets.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P024 - Bracket checker\n\nChecks the round, square and curly brackets of a typed text of up to 20 lines.\nIt finds the first error and says where it is and why: a closing bracket with\nnothing to close, a closing bracket of the wrong kind, or brackets still open\nwhen the text ends. For a text whose brackets all match, it gives the number\nof pairs of each kind and the deepest nesting, and it can outline the text by\nits brackets.\n\n- `main.eml` - the menu, reading the lines of a text, and what the screen shows\n- `brackets.eml` - the scan for the first error and the deepest nesting, and\n  the outline\n- `text.eml` - trimming, spaces, lists in words and plurals\n\nHow each part works:\n\n- The scan: every opening bracket goes on a stack with its line and column.\n  A closing bracket must match the bracket on top of the stack, which it\n  removes. The first one that does not - because the stack is empty, or\n  because the top is another kind - is the error; the line is shown with a\n  caret under the column. Brackets still on the stack at the end were never\n  closed: they are listed outermost first (five at most, then \"and N more\"),\n  with the closing brackets they need, innermost first.\n- The deepest nesting is the stack's greatest height, with the line and\n  column of the bracket that first reached it.\n- The outline joins the lines with spaces and breaks the text after every\n  opening bracket and before every closing one, indenting each piece two\n  spaces per level. An empty pair such as `()` or `[]` stays where it is.\n\nWhat is checked: a text is 1 to 20 lines, ended by an empty line (or a line\nof spaces); a line longer than 120 characters is asked for again; at 20 lines\nthe text ends by itself. The outline needs every bracket matched. A menu\nchoice other than 1, 2 or 3 is asked again.\n\nSessions: `sessions/basic.in` checks a three-line `if` block (6 pairs, deepest\nnesting 3), outlines a JSON object with nested lists and objects, finds\n`print(a[1)]` closing a `[` with `)`, finds a `(` and a `[` left open at the\nend of a function, and outlines a `while` loop; `sessions/bad-input.in` types\nmenu choices that are not 1 to 3, empty texts, a `)` with nothing to close,\ntext with no brackets to check or to outline, a mismatch where an outline was\nasked for, seven nested pairs, a line of 121 characters, six brackets never\nclosed, and 20 lines.\n\nBuilt on the verified corpus cases `balanced-brackets-checker` (a list used as\na stack: push on an opener, compare and pop on a closer) and\n`bracket-tree-outline` (a bracketed text shown as an indented outline).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P024 bracket checker: the first bracket error in a text, with its line and\n# column, the deepest nesting, and an outline of the text by its brackets.\nimport brackets\nimport text\n\n20 => most_lines\n120 => widest_line\n\ndef read_text():\n    # Lines until an empty one: at most most_lines, each at most widest_line\n    # characters. Columns count from the start of a line as typed.\n    (\"Type the text one line at a time (at most \" + str(most_lines) + \" lines); an empty line ends it.\") ^0\n    [] => lines\n    while len(lines) < most_lines:\n        input(\"> \") => line\n        if text.trim(line) == \"\":\n            return lines\n        if len(line) > widest_line:\n            (\"That line has \" + str(len(line)) + \" characters; a line can have at most \" + str(widest_line) + \". Type it again.\") ^0\n        else:\n            lines + [line] => lines\n    (\"That is \" + str(most_lines) + \" lines, the most a text can have.\") ^0\n    return lines\n\ndef where(line_no, col):\n    return \"line \" + str(line_no) + \", column \" + str(col)\n\ndef show_at(lines, line_no, col):\n    # The line, numbered, with a caret under the column.\n    str(line_no) => num\n    (\"  \" + num + \" | \" + lines[line_no - 1]) ^0\n    (\"  \" + text.spaces(len(num)) + \" | \" + text.spaces(col - 1) + \"^\") ^0\n\ndef pair_kinds(pairs):\n    [] => parts\n    [\"( )\", \"[ ]\", \"{ }\"] => shapes\n    for k in [0:2]:\n        if pairs[k] > 0:\n            parts + [str(pairs[k]) + \" \" + shapes[k]] => parts\n    return text.listed(parts)\n\ndef report(lines, r):\n    # Shows a scan result; True when every bracket matched.\n    if r[0] == \"stray\":\n        (\"Line \" + str(r[1]) + \", column \" + str(r[2]) + \": '\" + r[3] + \"' has nothing to close.\") ^0\n        show_at(lines, r[1], r[2])\n        return False\n    if r[0] == \"mismatch\":\n        (\"Line \" + str(r[1]) + \", column \" + str(r[2]) + \": '\" + r[3] + \"' does not match the '\" + r[4] + \"' opened at \" + where(r[5], r[6]) + \".\") ^0\n        show_at(lines, r[1], r[2])\n        return False\n    if r[0] == \"unclosed\":\n        r[1] => still_open\n        if len(still_open) == 1:\n            (\"End of text: the '\" + still_open[0][0] + \"' opened at \" + where(still_open[0][1], still_open[0][2]) + \" is never closed.\") ^0\n            (\"Add \" + brackets.still_to_close(still_open) + \" to close it.\") ^0\n        else:\n            [] => named\n            for o in still_open:\n                if len(named) < 5:\n                    named + [\"'\" + o[0] + \"' at \" + where(o[1], o[2])] => named\n            if len(still_open) > 5:\n                named + [str(len(still_open) - 5) + \" more\"] => named\n            (\"End of text: \" + str(len(still_open)) + \" brackets are never closed - \" + text.listed(named) + \".\") ^0\n            (\"Add \" + brackets.still_to_close(still_open) + \" to close them, innermost first.\") ^0\n        return False\n    r[1] => pairs\n    pairs[0] + pairs[1] + pairs[2] => total\n    if total == 0:\n        (\"No brackets in \" + text.plural(len(lines), \"line\") + \".\") ^0\n    else:\n        (text.plural(len(lines), \"line\") + \", \" + text.plural(total, \"pair\") + \" of brackets: \" + pair_kinds(pairs) + \".\") ^0\n        (\"Deepest nesting: \" + str(r[2]) + \", first reached at \" + where(r[3], r[4]) + \".\") ^0\n        \"Every bracket is closed, in the right order.\" ^0\n    return True\n\nTrue => running\nwhile running:\n    \"\" ^0\n    \"== Bracket checker ==\" ^0\n    \"1) check a text  2) outline a text  3) quit\" ^0\n    text.trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        read_text() => lines\n        if len(lines) == 0:\n            \"No text.\" ^0\n        else:\n            report(lines, brackets.scan(lines)) => ok\n    elif choice == \"2\":\n        read_text() => lines\n        if len(lines) == 0:\n            \"No text.\" ^0\n        else:\n            brackets.scan(lines) => r\n            if r[0] != \"ok\":\n                report(lines, r) => ok\n                \"The outline needs every bracket matched.\" ^0\n            elif r[1][0] + r[1][1] + r[1][2] == 0:\n                \"No brackets to outline.\" ^0\n            else:\n                for line in brackets.outline(lines):\n                    line ^0\n    elif choice == \"3\":\n        False => running\n    else:\n        \"Pick 1, 2 or 3.\" ^0\n\"Bye.\" ^0\n",
      "python": "import brackets\nimport text\nmost_lines = 20\nwidest_line = 120\n\ndef read_text():\n    print(\"Type the text one line at a time (at most \" + str(most_lines) + \" lines); an empty line ends it.\")\n    lines = []\n    while len(lines) < most_lines:\n        line = input(\"> \")\n        if text.trim(line) == \"\":\n            return lines\n        if len(line) > widest_line:\n            print(\"That line has \" + str(len(line)) + \" characters; a line can have at most \" + str(widest_line) + \". Type it again.\")\n        else:\n            lines = lines + [line]\n    print(\"That is \" + str(most_lines) + \" lines, the most a text can have.\")\n    return lines\n\ndef where(line_no, col):\n    return \"line \" + str(line_no) + \", column \" + str(col)\n\ndef show_at(lines, line_no, col):\n    num = str(line_no)\n    print(\"  \" + num + \" | \" + lines[line_no - 1])\n    print(\"  \" + text.spaces(len(num)) + \" | \" + text.spaces(col - 1) + \"^\")\n\ndef pair_kinds(pairs):\n    parts = []\n    shapes = [\"( )\", \"[ ]\", \"{ }\"]\n    for k in range(0, 3):\n        if pairs[k] > 0:\n            parts = parts + [str(pairs[k]) + \" \" + shapes[k]]\n    return text.listed(parts)\n\ndef report(lines, r):\n    if r[0] == \"stray\":\n        print(\"Line \" + str(r[1]) + \", column \" + str(r[2]) + \": '\" + r[3] + \"' has nothing to close.\")\n        show_at(lines, r[1], r[2])\n        return False\n    if r[0] == \"mismatch\":\n        print(\"Line \" + str(r[1]) + \", column \" + str(r[2]) + \": '\" + r[3] + \"' does not match the '\" + r[4] + \"' opened at \" + where(r[5], r[6]) + \".\")\n        show_at(lines, r[1], r[2])\n        return False\n    if r[0] == \"unclosed\":\n        still_open = r[1]\n        if len(still_open) == 1:\n            print(\"End of text: the '\" + still_open[0][0] + \"' opened at \" + where(still_open[0][1], still_open[0][2]) + \" is never closed.\")\n            print(\"Add \" + brackets.still_to_close(still_open) + \" to close it.\")\n        else:\n            named = []\n            for o in still_open:\n                if len(named) < 5:\n                    named = named + [\"'\" + o[0] + \"' at \" + where(o[1], o[2])]\n            if len(still_open) > 5:\n                named = named + [str(len(still_open) - 5) + \" more\"]\n            print(\"End of text: \" + str(len(still_open)) + \" brackets are never closed - \" + text.listed(named) + \".\")\n            print(\"Add \" + brackets.still_to_close(still_open) + \" to close them, innermost first.\")\n        return False\n    pairs = r[1]\n    total = pairs[0] + pairs[1] + pairs[2]\n    if total == 0:\n        print(\"No brackets in \" + text.plural(len(lines), \"line\") + \".\")\n    else:\n        print(text.plural(len(lines), \"line\") + \", \" + text.plural(total, \"pair\") + \" of brackets: \" + pair_kinds(pairs) + \".\")\n        print(\"Deepest nesting: \" + str(r[2]) + \", first reached at \" + where(r[3], r[4]) + \".\")\n        print(\"Every bracket is closed, in the right order.\")\n    return True\n\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"== Bracket checker ==\")\n    print(\"1) check a text  2) outline a text  3) quit\")\n    choice = text.trim(input(\"choice> \"))\n    if choice == \"1\":\n        lines = read_text()\n        if len(lines) == 0:\n            print(\"No text.\")\n        else:\n            ok = report(lines, brackets.scan(lines))\n    elif choice == \"2\":\n        lines = read_text()\n        if len(lines) == 0:\n            print(\"No text.\")\n        else:\n            r = brackets.scan(lines)\n            if r[0] != \"ok\":\n                ok = report(lines, r)\n                print(\"The outline needs every bracket matched.\")\n            elif r[1][0] + r[1][1] + r[1][2] == 0:\n                print(\"No brackets to outline.\")\n            else:\n                for line in brackets.outline(lines):\n                    print(line)\n    elif choice == \"3\":\n        running = False\n    else:\n        print(\"Pick 1, 2 or 3.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "brackets.eml",
      "eml": "# P024 bracket checker - walking the brackets of a text: the first error, the\n# deepest nesting, and an outline that breaks the text at its brackets.\n\ndef closer_for(c):\n    # The bracket that closes c, or \"\" if c does not open one.\n    if c == \"(\":\n        return \")\"\n    if c == \"[\":\n        return \"]\"\n    if c == \"{\":\n        return \"}\"\n    return \"\"\n\ndef opener_for(c):\n    # The bracket that c closes, or \"\" if c does not close one.\n    if c == \")\":\n        return \"(\"\n    if c == \"]\":\n        return \"[\"\n    if c == \"}\":\n        return \"{\"\n    return \"\"\n\ndef kind_index(c):\n    # 0 for round, 1 for square, 2 for curly; c is an opening bracket.\n    if c == \"(\":\n        return 0\n    if c == \"[\":\n        return 1\n    return 2\n\ndef scan(lines):\n    # Every bracket in order, kept on a stack of [bracket, line, column]. The\n    # result is one of:\n    #   [\"ok\", pairs, deepest, line, column] - every bracket matched; pairs\n    #       counts round, square and curly pairs, and the deepest nesting is\n    #       first reached at line, column (both 0 when there are no brackets)\n    #   [\"stray\", line, column, closer] - a closing bracket with nothing open\n    #   [\"mismatch\", line, column, closer, opener, its line, its column]\n    #   [\"unclosed\", stack] - the text ends with these still open, outermost\n    #       first\n    [] => stack\n    [0, 0, 0] => pairs\n    0 => deepest\n    0 => deep_line\n    0 => deep_col\n    0 => li\n    while li < len(lines):\n        lines[li] => line\n        0 => ci\n        while ci < len(line):\n            line[ci] => c\n            if closer_for(c) != \"\":\n                stack + [[c, li + 1, ci + 1]] => stack\n                if len(stack) > deepest:\n                    len(stack) => deepest\n                    li + 1 => deep_line\n                    ci + 1 => deep_col\n            elif opener_for(c) != \"\":\n                if len(stack) == 0:\n                    return [\"stray\", li + 1, ci + 1, c]\n                stack[len(stack) - 1] => top\n                if top[0] != opener_for(c):\n                    return [\"mismatch\", li + 1, ci + 1, c, top[0], top[1], top[2]]\n                pairs[kind_index(top[0])] + 1 => pairs[kind_index(top[0])]\n                stack[0:len(stack) - 1] => stack\n            ci + 1 => ci\n        li + 1 => li\n    if len(stack) > 0:\n        return [\"unclosed\", stack]\n    return [\"ok\", pairs, deepest, deep_line, deep_col]\n\ndef still_to_close(stack):\n    # The closing brackets the open ones need, innermost first: \"])}\".\n    \"\" => out\n    len(stack) - 1 => i\n    while i >= 0:\n        out + closer_for(stack[i][0]) => out\n        i - 1 => i\n    return out\n\ndef trimmed(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef next_solid(s, i):\n    # The first position from i on that is not a space (len(s) if none).\n    while i < len(s) and s[i] == \" \":\n        i + 1 => i\n    return i\n\ndef outline(lines):\n    # The text, its lines joined by spaces, broken after every opening bracket\n    # and before every closing one, each piece indented two spaces per level.\n    # An empty pair such as () stays where it is. Only for text whose brackets\n    # all match.\n    \"\" => s\n    for line in lines:\n        if s != \"\":\n            s + \" \" => s\n        s + line => s\n    [] => out\n    \"\" => piece\n    0 => depth\n    0 => i\n    while i < len(s):\n        s[i] => c\n        if closer_for(c) != \"\":\n            next_solid(s, i + 1) => j\n            if j < len(s) and opener_for(s[j]) != \"\":\n                piece + s[i:j + 1] => piece\n                j + 1 => i\n            else:\n                out + [\"  \" * depth + trimmed(piece + c)] => out\n                \"\" => piece\n                depth + 1 => depth\n                i + 1 => i\n        elif opener_for(c) != \"\":\n            if trimmed(piece) != \"\":\n                out + [\"  \" * depth + trimmed(piece)] => out\n            depth - 1 => depth\n            c => piece\n            i + 1 => i\n        else:\n            if not (c == \" \" and piece == \"\"):\n                piece + c => piece\n            i + 1 => i\n    if trimmed(piece) != \"\":\n        out + [\"  \" * depth + trimmed(piece)] => out\n    return out\n",
      "python": "def closer_for(c):\n    if c == \"(\":\n        return \")\"\n    if c == \"[\":\n        return \"]\"\n    if c == \"{\":\n        return \"}\"\n    return \"\"\n\ndef opener_for(c):\n    if c == \")\":\n        return \"(\"\n    if c == \"]\":\n        return \"[\"\n    if c == \"}\":\n        return \"{\"\n    return \"\"\n\ndef kind_index(c):\n    if c == \"(\":\n        return 0\n    if c == \"[\":\n        return 1\n    return 2\n\ndef scan(lines):\n    stack = []\n    pairs = [0, 0, 0]\n    deepest = 0\n    deep_line = 0\n    deep_col = 0\n    li = 0\n    while li < len(lines):\n        line = lines[li]\n        ci = 0\n        while ci < len(line):\n            c = line[ci]\n            if closer_for(c) != \"\":\n                stack = stack + [[c, li + 1, ci + 1]]\n                if len(stack) > deepest:\n                    deepest = len(stack)\n                    deep_line = li + 1\n                    deep_col = ci + 1\n            elif opener_for(c) != \"\":\n                if len(stack) == 0:\n                    return [\"stray\", li + 1, ci + 1, c]\n                top = stack[len(stack) - 1]\n                if top[0] != opener_for(c):\n                    return [\"mismatch\", li + 1, ci + 1, c, top[0], top[1], top[2]]\n                pairs[kind_index(top[0])] = pairs[kind_index(top[0])] + 1\n                stack = stack[0:len(stack) - 1]\n            ci = ci + 1\n        li = li + 1\n    if len(stack) > 0:\n        return [\"unclosed\", stack]\n    return [\"ok\", pairs, deepest, deep_line, deep_col]\n\ndef still_to_close(stack):\n    out = \"\"\n    i = len(stack) - 1\n    while i >= 0:\n        out = out + closer_for(stack[i][0])\n        i = i - 1\n    return out\n\ndef trimmed(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef next_solid(s, i):\n    while i < len(s) and s[i] == \" \":\n        i = i + 1\n    return i\n\ndef outline(lines):\n    s = \"\"\n    for line in lines:\n        if s != \"\":\n            s = s + \" \"\n        s = s + line\n    out = []\n    piece = \"\"\n    depth = 0\n    i = 0\n    while i < len(s):\n        c = s[i]\n        if closer_for(c) != \"\":\n            j = next_solid(s, i + 1)\n            if j < len(s) and opener_for(s[j]) != \"\":\n                piece = piece + s[i:j + 1]\n                i = j + 1\n            else:\n                out = out + [\"  \" * depth + trimmed(piece + c)]\n                piece = \"\"\n                depth = depth + 1\n                i = i + 1\n        elif opener_for(c) != \"\":\n            if trimmed(piece) != \"\":\n                out = out + [\"  \" * depth + trimmed(piece)]\n            depth = depth - 1\n            piece = c\n            i = i + 1\n        else:\n            if not (c == \" \" and piece == \"\"):\n                piece = piece + c\n            i = i + 1\n    if trimmed(piece) != \"\":\n        out = out + [\"  \" * depth + trimmed(piece)]\n    return out\n"
    },
    {
      "name": "text.eml",
      "eml": "# P024 bracket checker - small text helpers. The interpreter that checks every\n# session does not run string methods yet, so these are written out here.\n\ndef trim(s):\n    # s without the spaces at either end.\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef spaces(n):\n    return \" \" * n\n\ndef listed(items):\n    # [\"a\", \"b\", \"c\"] as \"a, b and c\".\n    \"\" => out\n    0 => i\n    while i < len(items):\n        if i > 0 and i == len(items) - 1:\n            out + \" and \" => out\n        elif i > 0:\n            out + \", \" => out\n        out + items[i] => out\n        i + 1 => i\n    return out\n\ndef plural(n, word):\n    if n == 1:\n        return \"1 \" + word\n    return str(n) + \" \" + word + \"s\"\n",
      "python": "def trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef spaces(n):\n    return \" \" * n\n\ndef listed(items):\n    out = \"\"\n    i = 0\n    while i < len(items):\n        if i > 0 and i == len(items) - 1:\n            out = out + \" and \"\n        elif i > 0:\n            out = out + \", \"\n        out = out + items[i]\n        i = i + 1\n    return out\n\ndef plural(n, word):\n    if n == 1:\n        return \"1 \" + word\n    return str(n) + \" \" + word + \"s\"\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "7\nhello\n1\n\n2\n\n1\na = b)\n\n1\njust words, no brackets\nhere either\n\n2\nplain text\n\n2\n((a + b) * [c - d}\n\n2\n(((((((x)))))))\n\n1\n(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)\nshort (line)\n\n1\n{a\n[b\n(c\n{d\n[e\n(f\n\n1\nx1 = (1)\nx2 = (2)\nx3 = (3)\nx4 = (4)\nx5 = (5)\nx6 = (6)\nx7 = (7)\nx8 = (8)\nx9 = (9)\nx10 = (10)\nx11 = (11)\nx12 = (12)\nx13 = (13)\nx14 = (14)\nx15 = (15)\nx16 = (16)\nx17 = (17)\nx18 = (18)\nx19 = (19)\nx20 = (20)\n2\n   \n3\n",
      "screen": "\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 7\nPick 1, 2 or 3.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> hello\nPick 1, 2 or 3.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 1\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> \nNo text.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 2\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> \nNo text.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 1\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> a = b)\n> \nLine 1, column 6: ')' has nothing to close.\n  1 | a = b)\n    |      ^\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 1\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> just words, no brackets\n> here either\n> \nNo brackets in 2 lines.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 2\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> plain text\n> \nNo brackets to outline.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 2\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> ((a + b) * [c - d}\n> \nLine 1, column 18: '}' does not match the '[' opened at line 1, column 12.\n  1 | ((a + b) * [c - d}\n    |                  ^\nThe outline needs every bracket matched.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 2\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> (((((((x)))))))\n> \n(\n  (\n    (\n      (\n        (\n          (\n            (\n              x\n            )\n          )\n        )\n      )\n    )\n  )\n)\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 1\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)\nThat line has 121 characters; a line can have at most 120. Type it again.\n> short (line)\n> \n1 line, 1 pair of brackets: 1 ( ).\nDeepest nesting: 1, first reached at line 1, column 7.\nEvery bracket is closed, in the right order.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 1\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> {a\n> [b\n> (c\n> {d\n> [e\n> (f\n> \nEnd of text: 6 brackets are never closed - '{' at line 1, column 1, '[' at line 2, column 1, '(' at line 3, column 1, '{' at line 4, column 1, '[' at line 5, column 1 and 1 more.\nAdd )]})]} to close them, innermost first.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 1\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> x1 = (1)\n> x2 = (2)\n> x3 = (3)\n> x4 = (4)\n> x5 = (5)\n> x6 = (6)\n> x7 = (7)\n> x8 = (8)\n> x9 = (9)\n> x10 = (10)\n> x11 = (11)\n> x12 = (12)\n> x13 = (13)\n> x14 = (14)\n> x15 = (15)\n> x16 = (16)\n> x17 = (17)\n> x18 = (18)\n> x19 = (19)\n> x20 = (20)\nThat is 20 lines, the most a text can have.\n20 lines, 20 pairs of brackets: 20 ( ).\nDeepest nesting: 1, first reached at line 1, column 6.\nEvery bracket is closed, in the right order.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 2\nType the text one line at a time (at most 20 lines); an empty line ends it.\n>    \nNo text.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 3\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\nif (total > limit[0]) {\n  report(items[i], {name: \"x\"})\n}\n\n2\n{\"name\": \"Ana\", \"tags\": [\"a\", \"b\"], \"box\": {\"w\": 2, \"h\": [1, 2]}, \"none\": []}\n\n1\nprint(a[1)]\n\n1\ndef f(x):\n  return g(x, [1, 2\n\n2\nwhile (n > 0) { total = total + f(n, [n - 1]); n = n - 1 }\n\n3\n",
      "screen": "\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 1\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> if (total > limit[0]) {\n>   report(items[i], {name: \"x\"})\n> }\n> \n3 lines, 6 pairs of brackets: 2 ( ), 2 [ ] and 2 { }.\nDeepest nesting: 3, first reached at line 2, column 15.\nEvery bracket is closed, in the right order.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 2\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> {\"name\": \"Ana\", \"tags\": [\"a\", \"b\"], \"box\": {\"w\": 2, \"h\": [1, 2]}, \"none\": []}\n> \n{\n  \"name\": \"Ana\", \"tags\": [\n    \"a\", \"b\"\n  ], \"box\": {\n    \"w\": 2, \"h\": [\n      1, 2\n    ]\n  }, \"none\": []\n}\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 1\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> print(a[1)]\n> \nLine 1, column 10: ')' does not match the '[' opened at line 1, column 8.\n  1 | print(a[1)]\n    |          ^\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 1\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> def f(x):\n>   return g(x, [1, 2\n> \nEnd of text: 2 brackets are never closed - '(' at line 2, column 11 and '[' at line 2, column 15.\nAdd ]) to close them, innermost first.\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 2\nType the text one line at a time (at most 20 lines); an empty line ends it.\n> while (n > 0) { total = total + f(n, [n - 1]); n = n - 1 }\n> \nwhile (\n  n > 0\n) {\n  total = total + f(\n    n, [\n      n - 1\n    ]\n  ); n = n - 1\n}\n\n== Bracket checker ==\n1) check a text  2) outline a text  3) quit\nchoice> 3\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "balanced-brackets-checker",
      "caseId": "080-balanced-brackets-checker",
      "title": "Balanced brackets checker"
    },
    {
      "slug": "bracket-tree-outline",
      "caseId": "185-bracket-tree-outline",
      "title": "Bracket tree: the round trip is the proof"
    }
  ],
  "updated": "2026-10-05"
}
