{"id":"calculator","title":"A calculator","summary":"A calculator you type into, line by line. Split the text into tokens, build a tree of records that knows times comes before plus, add parentheses, minus signs and variables, and answer bad input with a message that says what's wrong and where.","after":"sealed-and-patterns","intro":"A calculator that reads what you type, such as `rate * (hours - 0.5)`, has a harder job than it looks. The line is only text: the program has to find the numbers and symbols in it, know that `*` comes before `-` unless parentheses say otherwise, remember names, and say clearly what's wrong when the text isn't a calculation. In this project you build that calculator, one line at a time.\n\nWhen it's finished, a session looks like this. The calculator reads these lines:\n\n```text\nrate = 12.5\nhours = 8\npay = rate * (hours - 0.5)\npay / 3\n-(2 + 3) * 4\npay / (hours - 8)\n2 + * 3\n```\n\nand answers each one:\n\n```text\nrate = 12.5\nhours = 8\npay = 93.75\n31.25\n-20\nError: division by zero\nError: expected a number at column 5\n```\n\nA line with `=` saves a value under a name. Every other line is calculated and its value printed. A mistake doesn't stop the program: it gets a message, and the next line is read as usual.\n\n### How it works\n\nThe calculator works in three stages. The first two are how javac reads your code too:\n\n1. The **lexer** splits the text into **tokens**: `rate`, `*`, `(`, `hours`, `-`, `0.5`, `)`. Each token knows its column, so a message can point at it.\n2. The **parser** builds a **tree** from the tokens, as records. In the tree of `2 + 3 * 4`, the multiplication sits under the addition, so it's calculated first.\n3. The **calculator** evaluates the tree with a switch over record patterns, as in the last challenge of module 58. There the tree was written by hand; here the parser builds it from text.\n\n### What you need\n\nEverything up to module 58: strings and `charAt` (module 16), lists (module 13), classes and objects (modules 17 to 19), hash maps (module 31), exceptions (module 42), recursion (module 55), records (module 56), switch expressions (module 57), and sealed interfaces with record patterns (module 58). The few library methods the project needs beyond these, such as `Character.isDigit`, are explained where they're used.\n\n### The milestones\n\n1. Tokens: the lexer splits a line into numbers and symbols.\n2. Left to right: the first calculations, one operator after another.\n3. Multiplication first: a parser that builds a tree with precedence.\n4. Parentheses and minus signs.\n5. Variables: saving values under names.\n6. Clear error messages for bad input.\n\nCalculations use `double`, so `7 / 2` is 3.5, and a whole number is shown without `.0`.\n","milestones":[{"id":"tokens","title":"Tokens","task":"A calculator can't work with `12 + 3.5` as one piece of text. Its first job is to split the line into **tokens**: the numbers and the symbols. Each token also keeps its **column**, the place in the line where it starts, counted from 1, so that error messages can point at it in the last milestone.\n\nThe token types are ready. `Token.java` is a sealed interface, and `NumberToken.java` and `SymbolToken.java` are its two records. In `Lexer.java`, write `tokenize(String line)`. It goes through the line one character at a time and returns the tokens in a list, in order:\n\n- A space is skipped.\n- A digit or a `.` starts a number, which goes on as long as digits and dots follow. `Double.parseDouble` turns the number's text into a double, and it becomes a `NumberToken` with the column of its first character.\n- `+`, `-`, `*` and `/` each become a `SymbolToken`.\n- For any other character, throw an `IllegalArgumentException`. The last milestone gives it a proper message.\n\n`Character.isDigit(c)` is true when the character `c` is a digit, such as 0 to 9. `main` is ready: it prints the tokens of each line it reads. The check runs:\n\n```java\nSystem.out.println(Lexer.tokenize(\"12 + 3.5\"));\n```\n\nand expects this (a record's `toString` shows the values of its parts):\n\n```\n[NumberToken[value=12.0, column=1], SymbolToken[symbol=+, column=4], NumberToken[value=3.5, column=6]]\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        while (scanner.hasNextLine()) {\n            String line = scanner.nextLine();\n            System.out.println(Lexer.tokenize(line));\n        }\n    }\n}\n\n// ==== Lexer.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Lexer {\n    public static List<Token> tokenize(String line) {\n        List<Token> tokens = new ArrayList<>();\n        return tokens;\n    }\n}\n\n// ==== Token.java ====\npublic sealed interface Token permits NumberToken, SymbolToken {\n}\n\n// ==== NumberToken.java ====\npublic record NumberToken(double value, int column) implements Token {\n}\n\n// ==== SymbolToken.java ====\npublic record SymbolToken(char symbol, int column) implements Token {\n}\n","solution":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        while (scanner.hasNextLine()) {\n            String line = scanner.nextLine();\n            System.out.println(Lexer.tokenize(line));\n        }\n    }\n}\n\n// ==== Lexer.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Lexer {\n    public static List<Token> tokenize(String line) {\n        List<Token> tokens = new ArrayList<>();\n        int i = 0;\n        while (i < line.length()) {\n            char c = line.charAt(i);\n            if (c == ' ') {\n                i++;\n            } else if (Character.isDigit(c) || c == '.') {\n                int start = i;\n                while (i < line.length() && (Character.isDigit(line.charAt(i)) || line.charAt(i) == '.')) {\n                    i++;\n                }\n                double value = Double.parseDouble(line.substring(start, i));\n                tokens.add(new NumberToken(value, start + 1));\n            } else if (\"+-*/\".indexOf(c) >= 0) {\n                tokens.add(new SymbolToken(c, i + 1));\n                i++;\n            } else {\n                throw new IllegalArgumentException(\"unexpected character \" + c);\n            }\n        }\n        return tokens;\n    }\n}\n\n// ==== Token.java ====\npublic sealed interface Token permits NumberToken, SymbolToken {\n}\n\n// ==== NumberToken.java ====\npublic record NumberToken(double value, int column) implements Token {\n}\n\n// ==== SymbolToken.java ====\npublic record SymbolToken(char symbol, int column) implements Token {\n}\n","hints":["Use an index `i` and a `while (i < line.length())` loop rather than a for loop: a number takes several characters, so each step moves `i` on by as many characters as it used.","For a number: remember `int start = i;`, move `i` on while it points at a digit or a dot (check `i < line.length()` first), and then `line.substring(start, i)` is the number's text. Its column is `start + 1`.","`\"+-*/\".indexOf(c) >= 0` is true when `c` is one of the four symbols."],"tests":[{"name":"Tokens of a sum","stdin":"","call":"System.out.println(Lexer.tokenize(\"12 + 3.5\"));\n","expect":"[NumberToken[value=12.0, column=1], SymbolToken[symbol=+, column=4], NumberToken[value=3.5, column=6]]","hidden":false},{"name":"Tokens without spaces","stdin":"","call":"System.out.println(Lexer.tokenize(\"7*8/2-1\"));\n","expect":"[NumberToken[value=7.0, column=1], SymbolToken[symbol=*, column=2], NumberToken[value=8.0, column=3], SymbolToken[symbol=/, column=4], NumberToken[value=2.0, column=5], SymbolToken[symbol=-, column=6], NumberToken[value=1.0, column=7]]","hidden":false},{"name":"Extra spaces, decimals and an empty line","stdin":"","call":"System.out.println(Lexer.tokenize(\"  100 /  4 \"));\nSystem.out.println(Lexer.tokenize(\"0.25-.5\"));\nSystem.out.println(Lexer.tokenize(\"\"));\n","expect":"[NumberToken[value=100.0, column=3], SymbolToken[symbol=/, column=7], NumberToken[value=4.0, column=10]]\n[NumberToken[value=0.25, column=1], SymbolToken[symbol=-, column=5], NumberToken[value=0.5, column=6]]\n[]","hidden":true}],"require":[{"pattern":"\\bnew\\s+NumberToken\\s*\\(","message":"Put each number in the list as a `new NumberToken(value, column)`."},{"pattern":"\\bnew\\s+SymbolToken\\s*\\(","message":"Put each symbol in the list as a `new SymbolToken(symbol, column)`."}],"forbid":[]},{"id":"left-to-right","title":"Left to right","task":"Now the calculator calculates. `Calculator.java` is new. Its `format` is ready: it turns a result into text without `.0` on whole numbers, so 15.0 becomes `15` and 3.5 stays `3.5`.\n\n1. In `Calculator.java`, write a method `apply(char operator, double left, double right)` with a switch expression: it returns `left + right` for `'+'`, `left - right` for `'-'`, and so on for `*` and `/`.\n2. Write `calculate(String line)`. It splits the line into tokens and works through them **from left to right**: the first number, then an operator and a number, again and again, using `apply` each time. It returns the result as text, made by `format`. For now, `2 + 3 * 4` gives 20, as (2 + 3) * 4 is. The next milestone teaches the calculator that multiplication comes first.\n3. Change `main`: it creates one `Calculator`, and prints what `calculate` returns for each line it reads. It skips blank lines: `line.trim().isEmpty()` is true for a line with nothing but spaces.\n\nYou may assume for now that every line is a correct calculation of numbers and the four operators. With the input:\n\n```text\n12 + 3.5\n10 - 4 - 3\n7 / 2\n2 * 3 * 4\n```\n\nthe program prints:\n\n```\n15.5\n3\n3.5\n24\n```\n","files":{"Calculator.java":"import java.util.List;\n\npublic class Calculator {\n\n    public String calculate(String line) {\n        List<Token> tokens = Lexer.tokenize(line);\n        return \"\";\n    }\n\n    // 15 becomes \"15\", 3.5 stays \"3.5\"\n    public static String format(double value) {\n        if (value == Math.floor(value) && Math.abs(value) < 1e15) {\n            return String.valueOf((long) value);\n        }\n        return String.valueOf(value);\n    }\n}\n"},"kind":"code","seed":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        while (scanner.hasNextLine()) {\n            String line = scanner.nextLine();\n            System.out.println(Lexer.tokenize(line));\n        }\n    }\n}\n\n// ==== Lexer.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Lexer {\n    public static List<Token> tokenize(String line) {\n        List<Token> tokens = new ArrayList<>();\n        int i = 0;\n        while (i < line.length()) {\n            char c = line.charAt(i);\n            if (c == ' ') {\n                i++;\n            } else if (Character.isDigit(c) || c == '.') {\n                int start = i;\n                while (i < line.length() && (Character.isDigit(line.charAt(i)) || line.charAt(i) == '.')) {\n                    i++;\n                }\n                double value = Double.parseDouble(line.substring(start, i));\n                tokens.add(new NumberToken(value, start + 1));\n            } else if (\"+-*/\".indexOf(c) >= 0) {\n                tokens.add(new SymbolToken(c, i + 1));\n                i++;\n            } else {\n                throw new IllegalArgumentException(\"unexpected character \" + c);\n            }\n        }\n        return tokens;\n    }\n}\n\n// ==== Token.java ====\npublic sealed interface Token permits NumberToken, SymbolToken {\n}\n\n// ==== NumberToken.java ====\npublic record NumberToken(double value, int column) implements Token {\n}\n\n// ==== SymbolToken.java ====\npublic record SymbolToken(char symbol, int column) implements Token {\n}\n\n// ==== Calculator.java ====\nimport java.util.List;\n\npublic class Calculator {\n\n    public String calculate(String line) {\n        List<Token> tokens = Lexer.tokenize(line);\n        return \"\";\n    }\n\n    // 15 becomes \"15\", 3.5 stays \"3.5\"\n    public static String format(double value) {\n        if (value == Math.floor(value) && Math.abs(value) < 1e15) {\n            return String.valueOf((long) value);\n        }\n        return String.valueOf(value);\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        Calculator calculator = new Calculator();\n        while (scanner.hasNextLine()) {\n            String line = scanner.nextLine();\n            if (!line.trim().isEmpty()) {\n                System.out.println(calculator.calculate(line));\n            }\n        }\n    }\n}\n\n// ==== Lexer.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Lexer {\n    public static List<Token> tokenize(String line) {\n        List<Token> tokens = new ArrayList<>();\n        int i = 0;\n        while (i < line.length()) {\n            char c = line.charAt(i);\n            if (c == ' ') {\n                i++;\n            } else if (Character.isDigit(c) || c == '.') {\n                int start = i;\n                while (i < line.length() && (Character.isDigit(line.charAt(i)) || line.charAt(i) == '.')) {\n                    i++;\n                }\n                double value = Double.parseDouble(line.substring(start, i));\n                tokens.add(new NumberToken(value, start + 1));\n            } else if (\"+-*/\".indexOf(c) >= 0) {\n                tokens.add(new SymbolToken(c, i + 1));\n                i++;\n            } else {\n                throw new IllegalArgumentException(\"unexpected character \" + c);\n            }\n        }\n        return tokens;\n    }\n}\n\n// ==== Token.java ====\npublic sealed interface Token permits NumberToken, SymbolToken {\n}\n\n// ==== NumberToken.java ====\npublic record NumberToken(double value, int column) implements Token {\n}\n\n// ==== SymbolToken.java ====\npublic record SymbolToken(char symbol, int column) implements Token {\n}\n\n// ==== Calculator.java ====\nimport java.util.List;\n\npublic class Calculator {\n\n    public String calculate(String line) {\n        List<Token> tokens = Lexer.tokenize(line);\n        double result = numberAt(tokens, 0);\n        int i = 1;\n        while (i + 1 < tokens.size()) {\n            if (tokens.get(i) instanceof SymbolToken operator) {\n                result = apply(operator.symbol(), result, numberAt(tokens, i + 1));\n            }\n            i += 2;\n        }\n        return format(result);\n    }\n\n    private double numberAt(List<Token> tokens, int index) {\n        if (tokens.get(index) instanceof NumberToken number) {\n            return number.value();\n        }\n        throw new IllegalArgumentException(\"expected a number\");\n    }\n\n    private double apply(char operator, double left, double right) {\n        return switch (operator) {\n            case '+' -> left + right;\n            case '-' -> left - right;\n            case '*' -> left * right;\n            default -> left / right;\n        };\n    }\n\n    // 15 becomes \"15\", 3.5 stays \"3.5\"\n    public static String format(double value) {\n        if (value == Math.floor(value) && Math.abs(value) < 1e15) {\n            return String.valueOf((long) value);\n        }\n        return String.valueOf(value);\n    }\n}\n","hints":["The numbers are at the indexes 0, 2, 4 and so on, and the operators at 1, 3, 5. Start `double result` with the number at index 0, then loop over the operators with `i += 2` and do `result = apply(symbol, result, the number after it)`.","A token is a NumberToken or a SymbolToken. `if (tokens.get(i) instanceof NumberToken number)` checks which and gives you `number.value()` in the same step.","A switch expression on a char: `return switch (operator) { case '+' -> left + right; ... default -> left / right; };`"],"tests":[{"name":"Four calculations","stdin":"12 + 3.5\n10 - 4 - 3\n7 / 2\n2 * 3 * 4\n","expect":"15.5\n3\n3.5\n24","hidden":false},{"name":"Left to right","stdin":"2 + 3 * 4\n","expect":"20","hidden":false},{"name":"A blank line and a long calculation","stdin":"1 / 4\n\n100 - 0.5 * 2\n9\n1 + 2 + 3 + 4 + 5\n","expect":"0.25\n199\n9\n15","hidden":true}],"require":[{"pattern":"\\bswitch\\s*\\(","message":"Choose the calculation in `apply` with a switch expression."},{"pattern":"\\bapply\\s*\\(","min":2,"message":"Write the method `apply(char operator, double left, double right)` and use it in `calculate`."}],"forbid":[]},{"id":"precedence","title":"Multiplication first","task":"In math, `2 + 3 * 4` is 14, because `*` and `/` come before `+` and `-`. Going from left to right can't do that. So the calculator first builds a **tree** of the expression, and then calculates the tree.\n\nThree new files hold the tree: `Expr.java` is a sealed interface, and its records are `Literal(double value)` for a number and `Operation(char operator, Expr left, Expr right)`. The tree of `2 + 3 * 4` is:\n\n```java\nnew Operation('+', new Literal(2), new Operation('*', new Literal(3), new Literal(4)))\n```\n\n`Parser.java` is new too. It reads the tokens with `next`, the index of the next token, and has one method per level:\n\n- `expression()` reads a term. Then, as long as the next token is `+` or `-`, it steps past that operator, reads another term, and joins the two: `result = new Operation(operator, result, term())`.\n- `term()` does the same one level lower: numbers joined by `*` and `/`.\n- `number()` and `nextSymbol()` are ready.\n\n`term()` reads all of `3 * 4` before `expression()` sees the rest, so the multiplication ends up lower in the tree, as one part of the addition.\n\nThen in `Calculator.java`:\n\n- Write `eval(Expr expr)`, which returns the value of a tree, with a switch over record patterns. An operation's value is `apply` on its operator and the values of its two parts.\n- Change `calculate` so that it returns `format(eval(new Parser(tokens).parse()))`. The left-to-right loop isn't needed any more: delete it, and any helper method only it used.\n\nWith the input:\n\n```text\n2 + 3 * 4\n10 - 6 / 2 - 1\n8 / 4 / 2\n```\n\nthe program prints:\n\n```\n14\n6\n1\n```\n","files":{"Expr.java":"public sealed interface Expr permits Literal, Operation {\n}\n","Literal.java":"public record Literal(double value) implements Expr {\n}\n","Operation.java":"public record Operation(char operator, Expr left, Expr right) implements Expr {\n}\n","Parser.java":"import java.util.List;\n\npublic class Parser {\n    private List<Token> tokens;\n    private int next;\n\n    public Parser(List<Token> tokens) {\n        this.tokens = tokens;\n        this.next = 0;\n    }\n\n    public Expr parse() {\n        return expression();\n    }\n\n    // terms joined by + and -\n    private Expr expression() {\n        return term();\n    }\n\n    // numbers joined by * and /\n    private Expr term() {\n        return number();\n    }\n\n    private Expr number() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof NumberToken number) {\n            this.next++;\n            return new Literal(number.value());\n        }\n        throw new IllegalArgumentException(\"expected a number\");\n    }\n\n    // the symbol of the next token, or a space when the next token isn't a symbol\n    private char nextSymbol() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof SymbolToken token) {\n            return token.symbol();\n        }\n        return ' ';\n    }\n}\n"},"replaces":["Left to right"],"kind":"code","seed":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        Calculator calculator = new Calculator();\n        while (scanner.hasNextLine()) {\n            String line = scanner.nextLine();\n            if (!line.trim().isEmpty()) {\n                System.out.println(calculator.calculate(line));\n            }\n        }\n    }\n}\n\n// ==== Lexer.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Lexer {\n    public static List<Token> tokenize(String line) {\n        List<Token> tokens = new ArrayList<>();\n        int i = 0;\n        while (i < line.length()) {\n            char c = line.charAt(i);\n            if (c == ' ') {\n                i++;\n            } else if (Character.isDigit(c) || c == '.') {\n                int start = i;\n                while (i < line.length() && (Character.isDigit(line.charAt(i)) || line.charAt(i) == '.')) {\n                    i++;\n                }\n                double value = Double.parseDouble(line.substring(start, i));\n                tokens.add(new NumberToken(value, start + 1));\n            } else if (\"+-*/\".indexOf(c) >= 0) {\n                tokens.add(new SymbolToken(c, i + 1));\n                i++;\n            } else {\n                throw new IllegalArgumentException(\"unexpected character \" + c);\n            }\n        }\n        return tokens;\n    }\n}\n\n// ==== Token.java ====\npublic sealed interface Token permits NumberToken, SymbolToken {\n}\n\n// ==== NumberToken.java ====\npublic record NumberToken(double value, int column) implements Token {\n}\n\n// ==== SymbolToken.java ====\npublic record SymbolToken(char symbol, int column) implements Token {\n}\n\n// ==== Calculator.java ====\nimport java.util.List;\n\npublic class Calculator {\n\n    public String calculate(String line) {\n        List<Token> tokens = Lexer.tokenize(line);\n        double result = numberAt(tokens, 0);\n        int i = 1;\n        while (i + 1 < tokens.size()) {\n            if (tokens.get(i) instanceof SymbolToken operator) {\n                result = apply(operator.symbol(), result, numberAt(tokens, i + 1));\n            }\n            i += 2;\n        }\n        return format(result);\n    }\n\n    private double numberAt(List<Token> tokens, int index) {\n        if (tokens.get(index) instanceof NumberToken number) {\n            return number.value();\n        }\n        throw new IllegalArgumentException(\"expected a number\");\n    }\n\n    private double apply(char operator, double left, double right) {\n        return switch (operator) {\n            case '+' -> left + right;\n            case '-' -> left - right;\n            case '*' -> left * right;\n            default -> left / right;\n        };\n    }\n\n    // 15 becomes \"15\", 3.5 stays \"3.5\"\n    public static String format(double value) {\n        if (value == Math.floor(value) && Math.abs(value) < 1e15) {\n            return String.valueOf((long) value);\n        }\n        return String.valueOf(value);\n    }\n}\n\n// ==== Expr.java ====\npublic sealed interface Expr permits Literal, Operation {\n}\n\n// ==== Literal.java ====\npublic record Literal(double value) implements Expr {\n}\n\n// ==== Operation.java ====\npublic record Operation(char operator, Expr left, Expr right) implements Expr {\n}\n\n// ==== Parser.java ====\nimport java.util.List;\n\npublic class Parser {\n    private List<Token> tokens;\n    private int next;\n\n    public Parser(List<Token> tokens) {\n        this.tokens = tokens;\n        this.next = 0;\n    }\n\n    public Expr parse() {\n        return expression();\n    }\n\n    // terms joined by + and -\n    private Expr expression() {\n        return term();\n    }\n\n    // numbers joined by * and /\n    private Expr term() {\n        return number();\n    }\n\n    private Expr number() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof NumberToken number) {\n            this.next++;\n            return new Literal(number.value());\n        }\n        throw new IllegalArgumentException(\"expected a number\");\n    }\n\n    // the symbol of the next token, or a space when the next token isn't a symbol\n    private char nextSymbol() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof SymbolToken token) {\n            return token.symbol();\n        }\n        return ' ';\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        Calculator calculator = new Calculator();\n        while (scanner.hasNextLine()) {\n            String line = scanner.nextLine();\n            if (!line.trim().isEmpty()) {\n                System.out.println(calculator.calculate(line));\n            }\n        }\n    }\n}\n\n// ==== Lexer.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Lexer {\n    public static List<Token> tokenize(String line) {\n        List<Token> tokens = new ArrayList<>();\n        int i = 0;\n        while (i < line.length()) {\n            char c = line.charAt(i);\n            if (c == ' ') {\n                i++;\n            } else if (Character.isDigit(c) || c == '.') {\n                int start = i;\n                while (i < line.length() && (Character.isDigit(line.charAt(i)) || line.charAt(i) == '.')) {\n                    i++;\n                }\n                double value = Double.parseDouble(line.substring(start, i));\n                tokens.add(new NumberToken(value, start + 1));\n            } else if (\"+-*/\".indexOf(c) >= 0) {\n                tokens.add(new SymbolToken(c, i + 1));\n                i++;\n            } else {\n                throw new IllegalArgumentException(\"unexpected character \" + c);\n            }\n        }\n        return tokens;\n    }\n}\n\n// ==== Token.java ====\npublic sealed interface Token permits NumberToken, SymbolToken {\n}\n\n// ==== NumberToken.java ====\npublic record NumberToken(double value, int column) implements Token {\n}\n\n// ==== SymbolToken.java ====\npublic record SymbolToken(char symbol, int column) implements Token {\n}\n\n// ==== Calculator.java ====\npublic class Calculator {\n\n    public String calculate(String line) {\n        Expr expr = new Parser(Lexer.tokenize(line)).parse();\n        return format(eval(expr));\n    }\n\n    private double eval(Expr expr) {\n        return switch (expr) {\n            case Literal(double value) -> value;\n            case Operation(char operator, Expr left, Expr right) -> apply(operator, eval(left), eval(right));\n        };\n    }\n\n    private double apply(char operator, double left, double right) {\n        return switch (operator) {\n            case '+' -> left + right;\n            case '-' -> left - right;\n            case '*' -> left * right;\n            default -> left / right;\n        };\n    }\n\n    // 15 becomes \"15\", 3.5 stays \"3.5\"\n    public static String format(double value) {\n        if (value == Math.floor(value) && Math.abs(value) < 1e15) {\n            return String.valueOf((long) value);\n        }\n        return String.valueOf(value);\n    }\n}\n\n// ==== Expr.java ====\npublic sealed interface Expr permits Literal, Operation {\n}\n\n// ==== Literal.java ====\npublic record Literal(double value) implements Expr {\n}\n\n// ==== Operation.java ====\npublic record Operation(char operator, Expr left, Expr right) implements Expr {\n}\n\n// ==== Parser.java ====\nimport java.util.List;\n\npublic class Parser {\n    private List<Token> tokens;\n    private int next;\n\n    public Parser(List<Token> tokens) {\n        this.tokens = tokens;\n        this.next = 0;\n    }\n\n    public Expr parse() {\n        return expression();\n    }\n\n    // terms joined by + and -\n    private Expr expression() {\n        Expr result = term();\n        while (nextSymbol() == '+' || nextSymbol() == '-') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, term());\n        }\n        return result;\n    }\n\n    // numbers joined by * and /\n    private Expr term() {\n        Expr result = number();\n        while (nextSymbol() == '*' || nextSymbol() == '/') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, number());\n        }\n        return result;\n    }\n\n    private Expr number() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof NumberToken number) {\n            this.next++;\n            return new Literal(number.value());\n        }\n        throw new IllegalArgumentException(\"expected a number\");\n    }\n\n    // the symbol of the next token, or a space when the next token isn't a symbol\n    private char nextSymbol() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof SymbolToken token) {\n            return token.symbol();\n        }\n        return ' ';\n    }\n}\n","hints":["In `expression()`: `Expr result = term();`, then `while (nextSymbol() == '+' || nextSymbol() == '-')`. Inside, remember `char operator = nextSymbol();`, step past it with `this.next++`, and join `result` with the next term.","`term()` is `expression()` with `number()` in place of `term()`, and `*` and `/` in place of `+` and `-`.","In eval: `case Literal(double value) -> value;` and `case Operation(char operator, Expr left, Expr right) -> apply(operator, eval(left), eval(right));`"],"tests":[{"name":"Times before plus","stdin":"2 + 3 * 4\n10 - 6 / 2 - 1\n8 / 4 / 2\n","expect":"14\n6\n1","hidden":false},{"name":"The tree of 2 + 3 * 4","stdin":"","call":"System.out.println(new Parser(Lexer.tokenize(\"2 + 3 * 4\")).parse());\n","expect":"Operation[operator=+, left=Literal[value=2.0], right=Operation[operator=*, left=Literal[value=3.0], right=Literal[value=4.0]]]","hidden":false},{"name":"Mixed operators","stdin":"1 + 2 * 3 - 4 / 8\n100 / 10 * 3\n5 - 2 - 1\n7\n","expect":"6.5\n30\n2\n7","hidden":true},{"name":"The tree of 9 - 3 - 2 * 5","stdin":"","call":"System.out.println(new Parser(Lexer.tokenize(\"9 - 3 - 2 * 5\")).parse());\n","expect":"Operation[operator=-, left=Operation[operator=-, left=Literal[value=9.0], right=Literal[value=3.0]], right=Operation[operator=*, left=Literal[value=2.0], right=Literal[value=5.0]]]","hidden":true}],"require":[{"pattern":"\\bcase\\s+Literal\\s*\\(","message":"Give eval a case with a record pattern for a Literal: `case Literal(double value) -> ...`."},{"pattern":"\\bcase\\s+Operation\\s*\\(","message":"Give eval a case with a record pattern for an Operation: `case Operation(char operator, Expr left, Expr right) -> ...`."},{"pattern":"\\bnew\\s+Operation\\s*\\(","min":2,"message":"Build the tree in Parser: `expression()` and `term()` each create a `new Operation(...)`."}],"forbid":[]},{"id":"parentheses","title":"Parentheses and minus signs","task":"Parentheses say what is calculated first: `(2 + 3) * 4` is 20. And a minus sign can stand in front of a number or a parenthesis: `-5`, `2 - -3`, `-(2 + 3)`.\n\n- In `Lexer.java`, `(` and `)` become SymbolTokens too.\n- `Negate.java` is new: the record `Negate(Expr inner)` is a part of the tree with a minus in front. The program doesn't compile when this milestone starts: javac says that Negate isn't allowed to implement Expr. Add it to Expr's `permits` list. Then javac says that the switch in `eval` doesn't cover all possible input values: give it a case for Negate, whose value is minus the value of `inner`.\n- In `Parser.java`, rename `number()` to `factor()` (term() calls it), and teach it two more things before it looks for a number:\n  - `(`: step past it, read a whole `expression()`, and then a `)` must come: step past that too. The expression inside is the factor's tree. If the `)` is missing, throw an IllegalArgumentException for now.\n  - `-`: step past it and return `new Negate(factor())`, the factor after it with a minus in front.\n\nThe parser now calls itself in a circle: `expression()` calls `term()`, `term()` calls `factor()`, and `factor()` calls `expression()` for the part in parentheses. That's recursion, as in module 55, and a number is its base case.\n\nWith the input:\n\n```text\n(2 + 3) * 4\n2 * (10 - (3 + 1))\n(1 + 2) / (4 - 1.5)\n```\n\nthe program prints:\n\n```\n20\n12\n1.2\n```\n","files":{"Negate.java":"public record Negate(Expr inner) implements Expr {\n}\n"},"kind":"code","seed":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        Calculator calculator = new Calculator();\n        while (scanner.hasNextLine()) {\n            String line = scanner.nextLine();\n            if (!line.trim().isEmpty()) {\n                System.out.println(calculator.calculate(line));\n            }\n        }\n    }\n}\n\n// ==== Lexer.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Lexer {\n    public static List<Token> tokenize(String line) {\n        List<Token> tokens = new ArrayList<>();\n        int i = 0;\n        while (i < line.length()) {\n            char c = line.charAt(i);\n            if (c == ' ') {\n                i++;\n            } else if (Character.isDigit(c) || c == '.') {\n                int start = i;\n                while (i < line.length() && (Character.isDigit(line.charAt(i)) || line.charAt(i) == '.')) {\n                    i++;\n                }\n                double value = Double.parseDouble(line.substring(start, i));\n                tokens.add(new NumberToken(value, start + 1));\n            } else if (\"+-*/\".indexOf(c) >= 0) {\n                tokens.add(new SymbolToken(c, i + 1));\n                i++;\n            } else {\n                throw new IllegalArgumentException(\"unexpected character \" + c);\n            }\n        }\n        return tokens;\n    }\n}\n\n// ==== Token.java ====\npublic sealed interface Token permits NumberToken, SymbolToken {\n}\n\n// ==== NumberToken.java ====\npublic record NumberToken(double value, int column) implements Token {\n}\n\n// ==== SymbolToken.java ====\npublic record SymbolToken(char symbol, int column) implements Token {\n}\n\n// ==== Calculator.java ====\npublic class Calculator {\n\n    public String calculate(String line) {\n        Expr expr = new Parser(Lexer.tokenize(line)).parse();\n        return format(eval(expr));\n    }\n\n    private double eval(Expr expr) {\n        return switch (expr) {\n            case Literal(double value) -> value;\n            case Operation(char operator, Expr left, Expr right) -> apply(operator, eval(left), eval(right));\n        };\n    }\n\n    private double apply(char operator, double left, double right) {\n        return switch (operator) {\n            case '+' -> left + right;\n            case '-' -> left - right;\n            case '*' -> left * right;\n            default -> left / right;\n        };\n    }\n\n    // 15 becomes \"15\", 3.5 stays \"3.5\"\n    public static String format(double value) {\n        if (value == Math.floor(value) && Math.abs(value) < 1e15) {\n            return String.valueOf((long) value);\n        }\n        return String.valueOf(value);\n    }\n}\n\n// ==== Expr.java ====\npublic sealed interface Expr permits Literal, Operation {\n}\n\n// ==== Literal.java ====\npublic record Literal(double value) implements Expr {\n}\n\n// ==== Operation.java ====\npublic record Operation(char operator, Expr left, Expr right) implements Expr {\n}\n\n// ==== Parser.java ====\nimport java.util.List;\n\npublic class Parser {\n    private List<Token> tokens;\n    private int next;\n\n    public Parser(List<Token> tokens) {\n        this.tokens = tokens;\n        this.next = 0;\n    }\n\n    public Expr parse() {\n        return expression();\n    }\n\n    // terms joined by + and -\n    private Expr expression() {\n        Expr result = term();\n        while (nextSymbol() == '+' || nextSymbol() == '-') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, term());\n        }\n        return result;\n    }\n\n    // numbers joined by * and /\n    private Expr term() {\n        Expr result = number();\n        while (nextSymbol() == '*' || nextSymbol() == '/') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, number());\n        }\n        return result;\n    }\n\n    private Expr number() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof NumberToken number) {\n            this.next++;\n            return new Literal(number.value());\n        }\n        throw new IllegalArgumentException(\"expected a number\");\n    }\n\n    // the symbol of the next token, or a space when the next token isn't a symbol\n    private char nextSymbol() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof SymbolToken token) {\n            return token.symbol();\n        }\n        return ' ';\n    }\n}\n\n// ==== Negate.java ====\npublic record Negate(Expr inner) implements Expr {\n}\n","solution":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        Calculator calculator = new Calculator();\n        while (scanner.hasNextLine()) {\n            String line = scanner.nextLine();\n            if (!line.trim().isEmpty()) {\n                System.out.println(calculator.calculate(line));\n            }\n        }\n    }\n}\n\n// ==== Lexer.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Lexer {\n    public static List<Token> tokenize(String line) {\n        List<Token> tokens = new ArrayList<>();\n        int i = 0;\n        while (i < line.length()) {\n            char c = line.charAt(i);\n            if (c == ' ') {\n                i++;\n            } else if (Character.isDigit(c) || c == '.') {\n                int start = i;\n                while (i < line.length() && (Character.isDigit(line.charAt(i)) || line.charAt(i) == '.')) {\n                    i++;\n                }\n                double value = Double.parseDouble(line.substring(start, i));\n                tokens.add(new NumberToken(value, start + 1));\n            } else if (\"+-*/()\".indexOf(c) >= 0) {\n                tokens.add(new SymbolToken(c, i + 1));\n                i++;\n            } else {\n                throw new IllegalArgumentException(\"unexpected character \" + c);\n            }\n        }\n        return tokens;\n    }\n}\n\n// ==== Token.java ====\npublic sealed interface Token permits NumberToken, SymbolToken {\n}\n\n// ==== NumberToken.java ====\npublic record NumberToken(double value, int column) implements Token {\n}\n\n// ==== SymbolToken.java ====\npublic record SymbolToken(char symbol, int column) implements Token {\n}\n\n// ==== Calculator.java ====\npublic class Calculator {\n\n    public String calculate(String line) {\n        Expr expr = new Parser(Lexer.tokenize(line)).parse();\n        return format(eval(expr));\n    }\n\n    private double eval(Expr expr) {\n        return switch (expr) {\n            case Literal(double value) -> value;\n            case Negate(Expr inner) -> -eval(inner);\n            case Operation(char operator, Expr left, Expr right) -> apply(operator, eval(left), eval(right));\n        };\n    }\n\n    private double apply(char operator, double left, double right) {\n        return switch (operator) {\n            case '+' -> left + right;\n            case '-' -> left - right;\n            case '*' -> left * right;\n            default -> left / right;\n        };\n    }\n\n    // 15 becomes \"15\", 3.5 stays \"3.5\"\n    public static String format(double value) {\n        if (value == Math.floor(value) && Math.abs(value) < 1e15) {\n            return String.valueOf((long) value);\n        }\n        return String.valueOf(value);\n    }\n}\n\n// ==== Expr.java ====\npublic sealed interface Expr permits Literal, Operation, Negate {\n}\n\n// ==== Literal.java ====\npublic record Literal(double value) implements Expr {\n}\n\n// ==== Operation.java ====\npublic record Operation(char operator, Expr left, Expr right) implements Expr {\n}\n\n// ==== Parser.java ====\nimport java.util.List;\n\npublic class Parser {\n    private List<Token> tokens;\n    private int next;\n\n    public Parser(List<Token> tokens) {\n        this.tokens = tokens;\n        this.next = 0;\n    }\n\n    public Expr parse() {\n        return expression();\n    }\n\n    // terms joined by + and -\n    private Expr expression() {\n        Expr result = term();\n        while (nextSymbol() == '+' || nextSymbol() == '-') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, term());\n        }\n        return result;\n    }\n\n    // factors joined by * and /\n    private Expr term() {\n        Expr result = factor();\n        while (nextSymbol() == '*' || nextSymbol() == '/') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, factor());\n        }\n        return result;\n    }\n\n    // a number, an expression in parentheses, or a factor with a minus in front\n    private Expr factor() {\n        if (nextSymbol() == '(') {\n            this.next++;\n            Expr inside = expression();\n            if (nextSymbol() != ')') {\n                throw new IllegalArgumentException(\"expected )\");\n            }\n            this.next++;\n            return inside;\n        }\n        if (nextSymbol() == '-') {\n            this.next++;\n            return new Negate(factor());\n        }\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof NumberToken number) {\n            this.next++;\n            return new Literal(number.value());\n        }\n        throw new IllegalArgumentException(\"expected a number\");\n    }\n\n    // the symbol of the next token, or a space when the next token isn't a symbol\n    private char nextSymbol() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof SymbolToken token) {\n            return token.symbol();\n        }\n        return ' ';\n    }\n}\n\n// ==== Negate.java ====\npublic record Negate(Expr inner) implements Expr {\n}\n","hints":["For `(`: `if (nextSymbol() == '(') { this.next++; Expr inside = expression(); ... }`. After the expression, `nextSymbol()` must be `')'`: step past it with `this.next++` and `return inside;`.","The minus in front: `if (nextSymbol() == '-') { this.next++; return new Negate(factor()); }`. Calling `factor()` again lets `- -3` and `-(2 + 3)` work too.","In eval: `case Negate(Expr inner) -> -eval(inner);`"],"tests":[{"name":"Parentheses first","stdin":"(2 + 3) * 4\n2 * (10 - (3 + 1))\n(1 + 2) / (4 - 1.5)\n","expect":"20\n12\n1.2","hidden":false},{"name":"Minus signs","stdin":"-5 + 2\n2 - -3\n-(2 + 3) * 2\n6 / -4\n","expect":"-3\n5\n-10\n-1.5","hidden":false},{"name":"The tree of -(1 + 2)","stdin":"","call":"System.out.println(new Parser(Lexer.tokenize(\"-(1 + 2)\")).parse());\n","expect":"Negate[inner=Operation[operator=+, left=Literal[value=1.0], right=Literal[value=2.0]]]","hidden":false},{"name":"Deep parentheses","stdin":"((7))\n-(-(4)) * (2 + 1) / 3\n((1 + 1) * (2 + 2)) - -0.5\n(10)/(4)\n","expect":"7\n4\n8.5\n2.5","hidden":true},{"name":"Tokens of parentheses","stdin":"","call":"System.out.println(Lexer.tokenize(\"(1)\"));\nSystem.out.println(Lexer.tokenize(\"-(2)*3\"));\n","expect":"[SymbolToken[symbol=(, column=1], NumberToken[value=1.0, column=2], SymbolToken[symbol=), column=3]]\n[SymbolToken[symbol=-, column=1], SymbolToken[symbol=(, column=2], NumberToken[value=2.0, column=3], SymbolToken[symbol=), column=4], SymbolToken[symbol=*, column=5], NumberToken[value=3.0, column=6]]","hidden":true}],"require":[{"pattern":"\\bcase\\s+Negate\\s*\\(","message":"Give eval a case with a record pattern for Negate: `case Negate(Expr inner) -> ...`."},{"pattern":"\\bnew\\s+Negate\\s*\\(","message":"In the parser, a minus in front of a factor becomes a `new Negate(...)`."}],"forbid":[]},{"id":"variables","title":"Variables","task":"A line such as `rate = 12.5` saves a value under a name, and later lines can use the name: `rate * 8`.\n\n- **Tokens.** `NameToken.java` is new: `NameToken(String name, int column)`. Add it to Token's `permits` list. In `Lexer.java`, a letter starts a name, which goes on as long as letters and digits follow, so `side2` is one name. `Character.isLetter(c)` is true for a letter, and `Character.isLetterOrDigit(c)` for a letter or a digit. `=` becomes a SymbolToken.\n- **The tree.** `Variable.java` is new: `Variable(String name)`. Add it to Expr's `permits` list. In `factor()`, a NameToken becomes a Variable.\n- **The values.** Calculator keeps them in a field, a map (a `HashMap<String, Double>`) created in a constructor, so that each line can use what the lines before it saved.\n- **Saving.** In `calculate`: when the first token is a name and the second is `=`, the tokens after them are an expression. Calculate it, save the value under the name, and return the name, ` = ` and the value, such as `pay = 80`. A name can get a new value, and its expression can use the old one: `x = x * 3`. `tokens.subList(2, tokens.size())` gives the tokens from index 2 on. Any other line is calculated as before.\n- **Using.** In `eval`, a Variable's value is the one saved under its name. For a name that has no value yet, throw an IllegalArgumentException for now.\n\nNames are case sensitive: `Total` and `total` are two variables. With the input:\n\n```text\nrate = 12.5\nhours = 8\nrate * hours\npay = rate * hours - 20\npay / 2\n```\n\nthe program prints:\n\n```\nrate = 12.5\nhours = 8\n100\npay = 80\n40\n```\n","files":{"NameToken.java":"public record NameToken(String name, int column) implements Token {\n}\n","Variable.java":"public record Variable(String name) implements Expr {\n}\n"},"kind":"code","seed":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        Calculator calculator = new Calculator();\n        while (scanner.hasNextLine()) {\n            String line = scanner.nextLine();\n            if (!line.trim().isEmpty()) {\n                System.out.println(calculator.calculate(line));\n            }\n        }\n    }\n}\n\n// ==== Lexer.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Lexer {\n    public static List<Token> tokenize(String line) {\n        List<Token> tokens = new ArrayList<>();\n        int i = 0;\n        while (i < line.length()) {\n            char c = line.charAt(i);\n            if (c == ' ') {\n                i++;\n            } else if (Character.isDigit(c) || c == '.') {\n                int start = i;\n                while (i < line.length() && (Character.isDigit(line.charAt(i)) || line.charAt(i) == '.')) {\n                    i++;\n                }\n                double value = Double.parseDouble(line.substring(start, i));\n                tokens.add(new NumberToken(value, start + 1));\n            } else if (\"+-*/()\".indexOf(c) >= 0) {\n                tokens.add(new SymbolToken(c, i + 1));\n                i++;\n            } else {\n                throw new IllegalArgumentException(\"unexpected character \" + c);\n            }\n        }\n        return tokens;\n    }\n}\n\n// ==== Token.java ====\npublic sealed interface Token permits NumberToken, SymbolToken {\n}\n\n// ==== NumberToken.java ====\npublic record NumberToken(double value, int column) implements Token {\n}\n\n// ==== SymbolToken.java ====\npublic record SymbolToken(char symbol, int column) implements Token {\n}\n\n// ==== Calculator.java ====\npublic class Calculator {\n\n    public String calculate(String line) {\n        Expr expr = new Parser(Lexer.tokenize(line)).parse();\n        return format(eval(expr));\n    }\n\n    private double eval(Expr expr) {\n        return switch (expr) {\n            case Literal(double value) -> value;\n            case Negate(Expr inner) -> -eval(inner);\n            case Operation(char operator, Expr left, Expr right) -> apply(operator, eval(left), eval(right));\n        };\n    }\n\n    private double apply(char operator, double left, double right) {\n        return switch (operator) {\n            case '+' -> left + right;\n            case '-' -> left - right;\n            case '*' -> left * right;\n            default -> left / right;\n        };\n    }\n\n    // 15 becomes \"15\", 3.5 stays \"3.5\"\n    public static String format(double value) {\n        if (value == Math.floor(value) && Math.abs(value) < 1e15) {\n            return String.valueOf((long) value);\n        }\n        return String.valueOf(value);\n    }\n}\n\n// ==== Expr.java ====\npublic sealed interface Expr permits Literal, Operation, Negate {\n}\n\n// ==== Literal.java ====\npublic record Literal(double value) implements Expr {\n}\n\n// ==== Operation.java ====\npublic record Operation(char operator, Expr left, Expr right) implements Expr {\n}\n\n// ==== Parser.java ====\nimport java.util.List;\n\npublic class Parser {\n    private List<Token> tokens;\n    private int next;\n\n    public Parser(List<Token> tokens) {\n        this.tokens = tokens;\n        this.next = 0;\n    }\n\n    public Expr parse() {\n        return expression();\n    }\n\n    // terms joined by + and -\n    private Expr expression() {\n        Expr result = term();\n        while (nextSymbol() == '+' || nextSymbol() == '-') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, term());\n        }\n        return result;\n    }\n\n    // factors joined by * and /\n    private Expr term() {\n        Expr result = factor();\n        while (nextSymbol() == '*' || nextSymbol() == '/') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, factor());\n        }\n        return result;\n    }\n\n    // a number, an expression in parentheses, or a factor with a minus in front\n    private Expr factor() {\n        if (nextSymbol() == '(') {\n            this.next++;\n            Expr inside = expression();\n            if (nextSymbol() != ')') {\n                throw new IllegalArgumentException(\"expected )\");\n            }\n            this.next++;\n            return inside;\n        }\n        if (nextSymbol() == '-') {\n            this.next++;\n            return new Negate(factor());\n        }\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof NumberToken number) {\n            this.next++;\n            return new Literal(number.value());\n        }\n        throw new IllegalArgumentException(\"expected a number\");\n    }\n\n    // the symbol of the next token, or a space when the next token isn't a symbol\n    private char nextSymbol() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof SymbolToken token) {\n            return token.symbol();\n        }\n        return ' ';\n    }\n}\n\n// ==== Negate.java ====\npublic record Negate(Expr inner) implements Expr {\n}\n\n// ==== NameToken.java ====\npublic record NameToken(String name, int column) implements Token {\n}\n\n// ==== Variable.java ====\npublic record Variable(String name) implements Expr {\n}\n","solution":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        Calculator calculator = new Calculator();\n        while (scanner.hasNextLine()) {\n            String line = scanner.nextLine();\n            if (!line.trim().isEmpty()) {\n                System.out.println(calculator.calculate(line));\n            }\n        }\n    }\n}\n\n// ==== Lexer.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Lexer {\n    public static List<Token> tokenize(String line) {\n        List<Token> tokens = new ArrayList<>();\n        int i = 0;\n        while (i < line.length()) {\n            char c = line.charAt(i);\n            if (c == ' ') {\n                i++;\n            } else if (Character.isDigit(c) || c == '.') {\n                int start = i;\n                while (i < line.length() && (Character.isDigit(line.charAt(i)) || line.charAt(i) == '.')) {\n                    i++;\n                }\n                double value = Double.parseDouble(line.substring(start, i));\n                tokens.add(new NumberToken(value, start + 1));\n            } else if (Character.isLetter(c)) {\n                int start = i;\n                while (i < line.length() && Character.isLetterOrDigit(line.charAt(i))) {\n                    i++;\n                }\n                tokens.add(new NameToken(line.substring(start, i), start + 1));\n            } else if (\"+-*/()=\".indexOf(c) >= 0) {\n                tokens.add(new SymbolToken(c, i + 1));\n                i++;\n            } else {\n                throw new IllegalArgumentException(\"unexpected character \" + c);\n            }\n        }\n        return tokens;\n    }\n}\n\n// ==== Token.java ====\npublic sealed interface Token permits NumberToken, SymbolToken, NameToken {\n}\n\n// ==== NumberToken.java ====\npublic record NumberToken(double value, int column) implements Token {\n}\n\n// ==== SymbolToken.java ====\npublic record SymbolToken(char symbol, int column) implements Token {\n}\n\n// ==== Calculator.java ====\nimport java.util.HashMap;\nimport java.util.List;\n\npublic class Calculator {\n    private HashMap<String, Double> variables;\n\n    public Calculator() {\n        this.variables = new HashMap<>();\n    }\n\n    public String calculate(String line) {\n        List<Token> tokens = Lexer.tokenize(line);\n        if (tokens.size() >= 2 && tokens.get(0) instanceof NameToken name && tokens.get(1) instanceof SymbolToken equals && equals.symbol() == '=') {\n            double value = eval(new Parser(tokens.subList(2, tokens.size())).parse());\n            this.variables.put(name.name(), value);\n            return name.name() + \" = \" + format(value);\n        }\n        Expr expr = new Parser(tokens).parse();\n        return format(eval(expr));\n    }\n\n    private double eval(Expr expr) {\n        return switch (expr) {\n            case Literal(double value) -> value;\n            case Variable(String name) -> valueOf(name);\n            case Negate(Expr inner) -> -eval(inner);\n            case Operation(char operator, Expr left, Expr right) -> apply(operator, eval(left), eval(right));\n        };\n    }\n\n    private double valueOf(String name) {\n        if (!this.variables.containsKey(name)) {\n            throw new IllegalArgumentException(\"unknown variable \" + name);\n        }\n        return this.variables.get(name);\n    }\n\n    private double apply(char operator, double left, double right) {\n        return switch (operator) {\n            case '+' -> left + right;\n            case '-' -> left - right;\n            case '*' -> left * right;\n            default -> left / right;\n        };\n    }\n\n    // 15 becomes \"15\", 3.5 stays \"3.5\"\n    public static String format(double value) {\n        if (value == Math.floor(value) && Math.abs(value) < 1e15) {\n            return String.valueOf((long) value);\n        }\n        return String.valueOf(value);\n    }\n}\n\n// ==== Expr.java ====\npublic sealed interface Expr permits Literal, Operation, Negate, Variable {\n}\n\n// ==== Literal.java ====\npublic record Literal(double value) implements Expr {\n}\n\n// ==== Operation.java ====\npublic record Operation(char operator, Expr left, Expr right) implements Expr {\n}\n\n// ==== Parser.java ====\nimport java.util.List;\n\npublic class Parser {\n    private List<Token> tokens;\n    private int next;\n\n    public Parser(List<Token> tokens) {\n        this.tokens = tokens;\n        this.next = 0;\n    }\n\n    public Expr parse() {\n        return expression();\n    }\n\n    // terms joined by + and -\n    private Expr expression() {\n        Expr result = term();\n        while (nextSymbol() == '+' || nextSymbol() == '-') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, term());\n        }\n        return result;\n    }\n\n    // factors joined by * and /\n    private Expr term() {\n        Expr result = factor();\n        while (nextSymbol() == '*' || nextSymbol() == '/') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, factor());\n        }\n        return result;\n    }\n\n    // a number, a name, an expression in parentheses, or a factor with a minus in front\n    private Expr factor() {\n        if (nextSymbol() == '(') {\n            this.next++;\n            Expr inside = expression();\n            if (nextSymbol() != ')') {\n                throw new IllegalArgumentException(\"expected )\");\n            }\n            this.next++;\n            return inside;\n        }\n        if (nextSymbol() == '-') {\n            this.next++;\n            return new Negate(factor());\n        }\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof NumberToken number) {\n            this.next++;\n            return new Literal(number.value());\n        }\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof NameToken name) {\n            this.next++;\n            return new Variable(name.name());\n        }\n        throw new IllegalArgumentException(\"expected a number\");\n    }\n\n    // the symbol of the next token, or a space when the next token isn't a symbol\n    private char nextSymbol() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof SymbolToken token) {\n            return token.symbol();\n        }\n        return ' ';\n    }\n}\n\n// ==== Negate.java ====\npublic record Negate(Expr inner) implements Expr {\n}\n\n// ==== NameToken.java ====\npublic record NameToken(String name, int column) implements Token {\n}\n\n// ==== Variable.java ====\npublic record Variable(String name) implements Expr {\n}\n","hints":["To check the first two tokens at once: `tokens.size() >= 2 && tokens.get(0) instanceof NameToken name && tokens.get(1) instanceof SymbolToken equals && equals.symbol() == '='`.","The parser doesn't need to know about `=`: give it only the tokens after it, `new Parser(tokens.subList(2, tokens.size())).parse()`.","In eval, `case Variable(String name) -> valueOf(name);` can call a method of your own that checks `this.variables.containsKey(name)` and returns `this.variables.get(name)`."],"tests":[{"name":"Pay for a shift","stdin":"rate = 12.5\nhours = 8\nrate * hours\npay = rate * hours - 20\npay / 2\n","expect":"rate = 12.5\nhours = 8\n100\npay = 80\n40","hidden":false},{"name":"A new value","stdin":"x = 2\nx = x * 3\nx + 1\n","expect":"x = 2\nx = 6\n7","hidden":false},{"name":"Tokens of a name","stdin":"","call":"System.out.println(Lexer.tokenize(\"side2 = 4\"));\n","expect":"[NameToken[name=side2, column=1], SymbolToken[symbol==, column=7], NumberToken[value=4.0, column=9]]","hidden":false},{"name":"An area","stdin":"width = 3\nheight = width + 1.5\narea = width * height / 2\n-area + (width)\n","expect":"width = 3\nheight = 4.5\narea = 6.75\n-3.75","hidden":true},{"name":"Names in capitals","stdin":"Total = 10\ntotal = 1\nTotal - total\nb2 = -(Total)\nb2*2\n","expect":"Total = 10\ntotal = 1\n9\nb2 = -10\n-20","hidden":true}],"require":[{"pattern":"\\b(Hash)?Map\\s*<\\s*String\\s*,\\s*Double\\s*>","message":"Keep the values in a map (a `HashMap<String, Double>`), with the names as keys."},{"pattern":"\\bcase\\s+Variable\\s*\\(","message":"Give eval a case with a record pattern for a Variable: `case Variable(String name) -> ...`."}],"forbid":[]},{"id":"errors","title":"Clear error messages","task":"Right now a typing mistake crashes the calculator. A good one says what's wrong and where, and goes on with the next line.\n\n`CalcException.java` is new: an unchecked exception that carries a message. Throw it instead of IllegalArgumentException, everywhere, with exactly these messages (the numbers and texts in them are examples):\n\n- `unexpected character # at column 5`: the lexer meets a character that can't be part of any token.\n- `bad number 1.2.3 at column 1`: `Double.parseDouble` can't read a number's text. It throws a NumberFormatException then: catch it in the lexer.\n- `expected a number at column 5`: `factor()` finds no number, name, `(` or `-`. When the line has ended, it's `expected a number at the end`. This also holds for `x =`, which is a save with nothing after the `=`.\n- `expected ) at column 8`, or `expected ) at the end`: a parenthesis isn't closed.\n- `expected an operator at column 3`: `parse()` has read a whole expression, but there are tokens left, as in `2 3`. Make `parse()` check for that.\n- `division by zero`: the right side of a `/` is 0.\n- `unknown variable cost`: a name without a value.\n\nThe column is the column of the token where the problem is. To ask any token for its column, add the method `int column();` to the Token interface: each of the three records already has a method of that name, so they implement it as they are.\n\n`main` catches CalcException, prints `Error: ` and the message, and goes on with the next line. A line with an error saves nothing. With the input:\n\n```text\n2 + # 3\n2 + * 3\n2 +\n(2 + 3\n2 3\n```\n\nthe program prints:\n\n```\nError: unexpected character # at column 5\nError: expected a number at column 5\nError: expected a number at the end\nError: expected ) at the end\nError: expected an operator at column 3\n```\n","files":{"CalcException.java":"public class CalcException extends RuntimeException {\n\n    public CalcException(String message) {\n        super(message);\n    }\n}\n"},"kind":"code","seed":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        Calculator calculator = new Calculator();\n        while (scanner.hasNextLine()) {\n            String line = scanner.nextLine();\n            if (!line.trim().isEmpty()) {\n                System.out.println(calculator.calculate(line));\n            }\n        }\n    }\n}\n\n// ==== Lexer.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Lexer {\n    public static List<Token> tokenize(String line) {\n        List<Token> tokens = new ArrayList<>();\n        int i = 0;\n        while (i < line.length()) {\n            char c = line.charAt(i);\n            if (c == ' ') {\n                i++;\n            } else if (Character.isDigit(c) || c == '.') {\n                int start = i;\n                while (i < line.length() && (Character.isDigit(line.charAt(i)) || line.charAt(i) == '.')) {\n                    i++;\n                }\n                double value = Double.parseDouble(line.substring(start, i));\n                tokens.add(new NumberToken(value, start + 1));\n            } else if (Character.isLetter(c)) {\n                int start = i;\n                while (i < line.length() && Character.isLetterOrDigit(line.charAt(i))) {\n                    i++;\n                }\n                tokens.add(new NameToken(line.substring(start, i), start + 1));\n            } else if (\"+-*/()=\".indexOf(c) >= 0) {\n                tokens.add(new SymbolToken(c, i + 1));\n                i++;\n            } else {\n                throw new IllegalArgumentException(\"unexpected character \" + c);\n            }\n        }\n        return tokens;\n    }\n}\n\n// ==== Token.java ====\npublic sealed interface Token permits NumberToken, SymbolToken, NameToken {\n}\n\n// ==== NumberToken.java ====\npublic record NumberToken(double value, int column) implements Token {\n}\n\n// ==== SymbolToken.java ====\npublic record SymbolToken(char symbol, int column) implements Token {\n}\n\n// ==== Calculator.java ====\nimport java.util.HashMap;\nimport java.util.List;\n\npublic class Calculator {\n    private HashMap<String, Double> variables;\n\n    public Calculator() {\n        this.variables = new HashMap<>();\n    }\n\n    public String calculate(String line) {\n        List<Token> tokens = Lexer.tokenize(line);\n        if (tokens.size() >= 2 && tokens.get(0) instanceof NameToken name && tokens.get(1) instanceof SymbolToken equals && equals.symbol() == '=') {\n            double value = eval(new Parser(tokens.subList(2, tokens.size())).parse());\n            this.variables.put(name.name(), value);\n            return name.name() + \" = \" + format(value);\n        }\n        Expr expr = new Parser(tokens).parse();\n        return format(eval(expr));\n    }\n\n    private double eval(Expr expr) {\n        return switch (expr) {\n            case Literal(double value) -> value;\n            case Variable(String name) -> valueOf(name);\n            case Negate(Expr inner) -> -eval(inner);\n            case Operation(char operator, Expr left, Expr right) -> apply(operator, eval(left), eval(right));\n        };\n    }\n\n    private double valueOf(String name) {\n        if (!this.variables.containsKey(name)) {\n            throw new IllegalArgumentException(\"unknown variable \" + name);\n        }\n        return this.variables.get(name);\n    }\n\n    private double apply(char operator, double left, double right) {\n        return switch (operator) {\n            case '+' -> left + right;\n            case '-' -> left - right;\n            case '*' -> left * right;\n            default -> left / right;\n        };\n    }\n\n    // 15 becomes \"15\", 3.5 stays \"3.5\"\n    public static String format(double value) {\n        if (value == Math.floor(value) && Math.abs(value) < 1e15) {\n            return String.valueOf((long) value);\n        }\n        return String.valueOf(value);\n    }\n}\n\n// ==== Expr.java ====\npublic sealed interface Expr permits Literal, Operation, Negate, Variable {\n}\n\n// ==== Literal.java ====\npublic record Literal(double value) implements Expr {\n}\n\n// ==== Operation.java ====\npublic record Operation(char operator, Expr left, Expr right) implements Expr {\n}\n\n// ==== Parser.java ====\nimport java.util.List;\n\npublic class Parser {\n    private List<Token> tokens;\n    private int next;\n\n    public Parser(List<Token> tokens) {\n        this.tokens = tokens;\n        this.next = 0;\n    }\n\n    public Expr parse() {\n        return expression();\n    }\n\n    // terms joined by + and -\n    private Expr expression() {\n        Expr result = term();\n        while (nextSymbol() == '+' || nextSymbol() == '-') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, term());\n        }\n        return result;\n    }\n\n    // factors joined by * and /\n    private Expr term() {\n        Expr result = factor();\n        while (nextSymbol() == '*' || nextSymbol() == '/') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, factor());\n        }\n        return result;\n    }\n\n    // a number, a name, an expression in parentheses, or a factor with a minus in front\n    private Expr factor() {\n        if (nextSymbol() == '(') {\n            this.next++;\n            Expr inside = expression();\n            if (nextSymbol() != ')') {\n                throw new IllegalArgumentException(\"expected )\");\n            }\n            this.next++;\n            return inside;\n        }\n        if (nextSymbol() == '-') {\n            this.next++;\n            return new Negate(factor());\n        }\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof NumberToken number) {\n            this.next++;\n            return new Literal(number.value());\n        }\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof NameToken name) {\n            this.next++;\n            return new Variable(name.name());\n        }\n        throw new IllegalArgumentException(\"expected a number\");\n    }\n\n    // the symbol of the next token, or a space when the next token isn't a symbol\n    private char nextSymbol() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof SymbolToken token) {\n            return token.symbol();\n        }\n        return ' ';\n    }\n}\n\n// ==== Negate.java ====\npublic record Negate(Expr inner) implements Expr {\n}\n\n// ==== NameToken.java ====\npublic record NameToken(String name, int column) implements Token {\n}\n\n// ==== Variable.java ====\npublic record Variable(String name) implements Expr {\n}\n\n// ==== CalcException.java ====\npublic class CalcException extends RuntimeException {\n\n    public CalcException(String message) {\n        super(message);\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        Calculator calculator = new Calculator();\n        while (scanner.hasNextLine()) {\n            String line = scanner.nextLine();\n            if (!line.trim().isEmpty()) {\n                try {\n                    System.out.println(calculator.calculate(line));\n                } catch (CalcException e) {\n                    System.out.println(\"Error: \" + e.getMessage());\n                }\n            }\n        }\n    }\n}\n\n// ==== Lexer.java ====\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Lexer {\n    public static List<Token> tokenize(String line) {\n        List<Token> tokens = new ArrayList<>();\n        int i = 0;\n        while (i < line.length()) {\n            char c = line.charAt(i);\n            if (c == ' ') {\n                i++;\n            } else if (Character.isDigit(c) || c == '.') {\n                int start = i;\n                while (i < line.length() && (Character.isDigit(line.charAt(i)) || line.charAt(i) == '.')) {\n                    i++;\n                }\n                String text = line.substring(start, i);\n                try {\n                    tokens.add(new NumberToken(Double.parseDouble(text), start + 1));\n                } catch (NumberFormatException e) {\n                    throw new CalcException(\"bad number \" + text + \" at column \" + (start + 1));\n                }\n            } else if (Character.isLetter(c)) {\n                int start = i;\n                while (i < line.length() && Character.isLetterOrDigit(line.charAt(i))) {\n                    i++;\n                }\n                tokens.add(new NameToken(line.substring(start, i), start + 1));\n            } else if (\"+-*/()=\".indexOf(c) >= 0) {\n                tokens.add(new SymbolToken(c, i + 1));\n                i++;\n            } else {\n                throw new CalcException(\"unexpected character \" + c + \" at column \" + (i + 1));\n            }\n        }\n        return tokens;\n    }\n}\n\n// ==== Token.java ====\npublic sealed interface Token permits NumberToken, SymbolToken, NameToken {\n    int column();\n}\n\n// ==== NumberToken.java ====\npublic record NumberToken(double value, int column) implements Token {\n}\n\n// ==== SymbolToken.java ====\npublic record SymbolToken(char symbol, int column) implements Token {\n}\n\n// ==== Calculator.java ====\nimport java.util.HashMap;\nimport java.util.List;\n\npublic class Calculator {\n    private HashMap<String, Double> variables;\n\n    public Calculator() {\n        this.variables = new HashMap<>();\n    }\n\n    public String calculate(String line) {\n        List<Token> tokens = Lexer.tokenize(line);\n        if (tokens.size() >= 2 && tokens.get(0) instanceof NameToken name && tokens.get(1) instanceof SymbolToken equals && equals.symbol() == '=') {\n            double value = eval(new Parser(tokens.subList(2, tokens.size())).parse());\n            this.variables.put(name.name(), value);\n            return name.name() + \" = \" + format(value);\n        }\n        Expr expr = new Parser(tokens).parse();\n        return format(eval(expr));\n    }\n\n    private double eval(Expr expr) {\n        return switch (expr) {\n            case Literal(double value) -> value;\n            case Variable(String name) -> valueOf(name);\n            case Negate(Expr inner) -> -eval(inner);\n            case Operation(char operator, Expr left, Expr right) -> apply(operator, eval(left), eval(right));\n        };\n    }\n\n    private double valueOf(String name) {\n        if (!this.variables.containsKey(name)) {\n            throw new CalcException(\"unknown variable \" + name);\n        }\n        return this.variables.get(name);\n    }\n\n    private double apply(char operator, double left, double right) {\n        return switch (operator) {\n            case '+' -> left + right;\n            case '-' -> left - right;\n            case '*' -> left * right;\n            default -> {\n                if (right == 0) {\n                    throw new CalcException(\"division by zero\");\n                }\n                yield left / right;\n            }\n        };\n    }\n\n    // 15 becomes \"15\", 3.5 stays \"3.5\"\n    public static String format(double value) {\n        if (value == Math.floor(value) && Math.abs(value) < 1e15) {\n            return String.valueOf((long) value);\n        }\n        return String.valueOf(value);\n    }\n}\n\n// ==== Expr.java ====\npublic sealed interface Expr permits Literal, Operation, Negate, Variable {\n}\n\n// ==== Literal.java ====\npublic record Literal(double value) implements Expr {\n}\n\n// ==== Operation.java ====\npublic record Operation(char operator, Expr left, Expr right) implements Expr {\n}\n\n// ==== Parser.java ====\nimport java.util.List;\n\npublic class Parser {\n    private List<Token> tokens;\n    private int next;\n\n    public Parser(List<Token> tokens) {\n        this.tokens = tokens;\n        this.next = 0;\n    }\n\n    public Expr parse() {\n        Expr result = expression();\n        if (this.next < this.tokens.size()) {\n            throw new CalcException(\"expected an operator \" + where());\n        }\n        return result;\n    }\n\n    // terms joined by + and -\n    private Expr expression() {\n        Expr result = term();\n        while (nextSymbol() == '+' || nextSymbol() == '-') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, term());\n        }\n        return result;\n    }\n\n    // factors joined by * and /\n    private Expr term() {\n        Expr result = factor();\n        while (nextSymbol() == '*' || nextSymbol() == '/') {\n            char operator = nextSymbol();\n            this.next++;\n            result = new Operation(operator, result, factor());\n        }\n        return result;\n    }\n\n    // a number, a name, an expression in parentheses, or a factor with a minus in front\n    private Expr factor() {\n        if (nextSymbol() == '(') {\n            this.next++;\n            Expr inside = expression();\n            if (nextSymbol() != ')') {\n                throw new CalcException(\"expected ) \" + where());\n            }\n            this.next++;\n            return inside;\n        }\n        if (nextSymbol() == '-') {\n            this.next++;\n            return new Negate(factor());\n        }\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof NumberToken number) {\n            this.next++;\n            return new Literal(number.value());\n        }\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof NameToken name) {\n            this.next++;\n            return new Variable(name.name());\n        }\n        throw new CalcException(\"expected a number \" + where());\n    }\n\n    // the symbol of the next token, or a space when the next token isn't a symbol\n    private char nextSymbol() {\n        if (this.next < this.tokens.size() && this.tokens.get(this.next) instanceof SymbolToken token) {\n            return token.symbol();\n        }\n        return ' ';\n    }\n\n    // where the next token is, for an error message\n    private String where() {\n        if (this.next < this.tokens.size()) {\n            return \"at column \" + this.tokens.get(this.next).column();\n        }\n        return \"at the end\";\n    }\n}\n\n// ==== Negate.java ====\npublic record Negate(Expr inner) implements Expr {\n}\n\n// ==== NameToken.java ====\npublic record NameToken(String name, int column) implements Token {\n}\n\n// ==== Variable.java ====\npublic record Variable(String name) implements Expr {\n}\n\n// ==== CalcException.java ====\npublic class CalcException extends RuntimeException {\n\n    public CalcException(String message) {\n        super(message);\n    }\n}\n","hints":["A helper in Parser saves writing the same thing three times: `where()` returns `\"at column \" + this.tokens.get(this.next).column()` when there's a next token, and `\"at the end\"` when there isn't.","Division by zero: in apply's switch, the case for `/` becomes a block, `{ if (right == 0) { throw new CalcException(\"division by zero\"); } yield left / right; }`.","CalcException extends RuntimeException, so the methods that throw it don't need `throws`. The try and catch go around the line in `main` that calls `calculate`, inside the loop."],"tests":[{"name":"Clear messages","stdin":"2 + # 3\n2 + * 3\n2 +\n(2 + 3\n2 3\n","expect":"Error: unexpected character # at column 5\nError: expected a number at column 5\nError: expected a number at the end\nError: expected ) at the end\nError: expected an operator at column 3","hidden":false},{"name":"Errors among calculations","stdin":"price = 4\nprice / (2 - 2)\ncost * 2\nprice * 2\n","expect":"price = 4\nError: division by zero\nError: unknown variable cost\n8","hidden":false},{"name":"Bad numbers and parentheses","stdin":"1.2.3 + 4\n(1 + 2 3\n2 + 3)\n","expect":"Error: bad number 1.2.3 at column 1\nError: expected ) at column 8\nError: expected an operator at column 6","hidden":false},{"name":"Empty sides and strange characters","stdin":"x = \n= 5\n5 % 2\nx = 1 / 0\nx\n4 / 0.5\n","expect":"Error: expected a number at the end\nError: expected a number at column 1\nError: unexpected character % at column 3\nError: division by zero\nError: unknown variable x\n8","hidden":true},{"name":"Lone dots and parentheses","stdin":".\n3 + .5\n((2)\n)\nk = 2 * (3\nk\n","expect":"Error: bad number . at column 1\n3.5\nError: expected ) at the end\nError: expected a number at column 1\nError: expected ) at the end\nError: unknown variable k","hidden":true}],"require":[{"pattern":"\\bcatch\\s*\\(\\s*CalcException\\b","message":"Catch the CalcException in `main`: `catch (CalcException e)`."},{"pattern":"\\bnew\\s+CalcException\\s*\\(","min":4,"message":"Make a CalcException for each kind of mistake: in the lexer, the parser and the calculator."},{"pattern":"\\bcatch\\s*\\(\\s*NumberFormatException\\b","message":"Catch the NumberFormatException of `Double.parseDouble` in the lexer, and throw a CalcException with the bad number instead."}],"forbid":[{"pattern":"\\bIllegalArgumentException\\b","message":"Throw a CalcException instead of an IllegalArgumentException."},{"pattern":"\\bcatch\\s*\\(\\s*(Exception|RuntimeException)\\b","message":"Catch only the exceptions you expect: CalcException in `main`, NumberFormatException in the lexer."}]}]}