STEPPER_REGISTRY.register("java-17/scanner", { label: 'Scanner and user input', panelConfig: { stack: true, heap: false, custom: { title: 'Input buffer', emptyText: 'waiting for user to type' }, legend: [ { color:'#1d5799', border:'#9dc0e8', label:'Currently running frame' }, { color:'#a0540a', border:'#e8c070', label:'Variable just assigned' }, { color:'#095e40', border:'#90d4b8', label:'Holding a value' }, { color:'#3d3490', border:'#b0a8e0', label:'Reference (object pointer)' }, ] }, scenarios: [ { label: "nextInt()", lines: [ { code: [['tok-kw','static '],['tok-kw','void '],['tok-meth','main'],['tok-op','(String[] args) {']] }, { code: [['',' '],['tok-type','Scanner '],['tok-op','sc = '],['tok-kw','new '],['tok-cls','Scanner'],['tok-op','(System.in);']] }, { code: [['',' '],['tok-type','int '],['tok-op','age = sc.'],['tok-meth','nextInt'],['tok-op','();']] }, { code: [['',' '],['tok-op','System.out.'],['tok-meth','println'],['tok-op','('],['tok-str','"Age: "'],['tok-op',' + age);']] }, { code: [['tok-op','}']] }, ], steps: [ { line:0, stack:[{name:'main()',vars:[],active:true}], custom:[], expl:{label:'Program starts', text:'Java enters main(). A frame is pushed onto the call stack. Nothing is in the input buffer yet.'} }, { line:1, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:true,cls:true}],active:true}], custom:[], expl:{label:'Scanner created', text:'new Scanner(System.in) creates a Scanner connected to the keyboard. sc holds a reference to it. The buffer is empty -- we have not asked for input yet.'} }, { line:2, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true}],active:true}], custom:[{name:'Input buffer',tag:'has data',rows:[{k:'buffered',v:'"25\\n"',fresh:true}]}], expl:{label:'User types input', text:'sc.nextInt() pauses and waits. The user types 25 and presses Enter. The text arrives in the buffer as 25\\n.'} }, { line:2, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:true,cls:false}],active:true}], custom:[{name:'Input buffer',tag:'leftover',rows:[{k:'buffered',v:'"\\n"',fresh:true}]}], expl:{label:'nextInt() reads the number', text:'nextInt() reads whitespace-separated tokens. It takes the 25 and converts it to an int. The trailing newline \\n is left behind in the buffer.'} }, { line:3, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:false,cls:false}],active:true}], custom:[{name:'Input buffer',tag:'leftover',rows:[{k:'buffered',v:'"\\n"',fresh:false}]}], expl:{label:'println runs', text:'age is 25. The program prints "Age: 25". The leftover \\n is still sitting in the buffer -- it will matter in the CRLF scenario.'} }, { line:4, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:false,cls:false}],active:true}], custom:[], expl:{label:'Done', text:'main() ends. Remember: nextInt() always leaves the newline in the buffer after reading.'} }, ] }, { label: "next() for Strings", lines: [ { code: [['tok-kw','static '],['tok-kw','void '],['tok-meth','main'],['tok-op','(String[] args) {']] }, { code: [['',' '],['tok-type','Scanner '],['tok-op','sc = '],['tok-kw','new '],['tok-cls','Scanner'],['tok-op','(System.in);']] }, { code: [['',' '],['tok-type','String '],['tok-op','name = sc.'],['tok-meth','next'],['tok-op','();']] }, { code: [['',' '],['tok-op','System.out.'],['tok-meth','println'],['tok-op','('],['tok-str','"Hello, "'],['tok-op',' + name);']] }, { code: [['tok-op','}']] }, ], steps: [ { line:0, stack:[{name:'main()',vars:[],active:true}], custom:[], expl:{label:'Program starts', text:'main() begins. Input buffer is empty.'} }, { line:1, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:true,cls:true}],active:true}], custom:[], expl:{label:'Scanner created', text:'new Scanner(System.in) connects to the keyboard. sc holds the reference.'} }, { line:2, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true}],active:true}], custom:[{name:'Input buffer',tag:'has data',rows:[{k:'buffered',v:'"Alice\\n"',fresh:true}]}], expl:{label:'User types input', text:'sc.next() pauses and waits. The user types Alice and presses Enter. next() reads up to the first whitespace -- it reads a single token.'} }, { line:2, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'name',v:'"Alice"',fresh:true,cls:false}],active:true}], custom:[{name:'Input buffer',tag:'leftover',rows:[{k:'buffered',v:'"\\n"',fresh:true}]}], expl:{label:'next() reads one token', text:'next() takes "Alice" and stops at the newline. The trailing \\n stays in the buffer -- same behavior as nextInt().'} }, { line:3, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'name',v:'"Alice"',fresh:false,cls:false}],active:true}], custom:[{name:'Input buffer',tag:'leftover',rows:[{k:'buffered',v:'"\\n"',fresh:false}]}], expl:{label:'println runs', text:'name is "Alice". The program prints "Hello, Alice".'} }, { line:4, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'name',v:'"Alice"',fresh:false,cls:false}],active:true}], custom:[], expl:{label:'Done', text:'main() ends. Both next() and nextInt() leave a newline behind. nextLine() behaves differently -- see the next two scenarios.'} }, ] }, { label: "CRLF bug", lines: [ { code: [['tok-kw','static '],['tok-kw','void '],['tok-meth','main'],['tok-op','(String[] args) {']] }, { code: [['',' '],['tok-type','Scanner '],['tok-op','sc = '],['tok-kw','new '],['tok-cls','Scanner'],['tok-op','(System.in);']] }, { code: [['',' '],['tok-type','int '],['tok-op','age = sc.'],['tok-meth','nextInt'],['tok-op','();']] }, { code: [['',' '],['tok-type','String '],['tok-op','name = sc.'],['tok-meth','nextLine'],['tok-op','();'],['',' '],['tok-cmt','// bug!']] }, { code: [['',' '],['tok-op','System.out.'],['tok-meth','println'],['tok-op','(name);']] }, { code: [['tok-op','}']] }, ], steps: [ { line:0, stack:[{name:'main()',vars:[],active:true}], custom:[], expl:{label:'Program starts', text:'main() begins. The goal: read an age, then read a name on the next line.'} }, { line:1, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:true,cls:true}],active:true}], custom:[], expl:{label:'Scanner created', text:'new Scanner(System.in) is ready.'} }, { line:2, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:true,cls:false}],active:true}], custom:[{name:'Input buffer',tag:'leftover',rows:[{k:'buffered',v:'"\\n"',fresh:true}]}], expl:{label:'nextInt() runs', text:'The user types 25 and presses Enter. nextInt() reads 25. The newline is left behind in the buffer.'} }, { line:3, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:false,cls:false},{n:'name',v:'""',fresh:true,cls:false}],active:true}], custom:[], expl:{label:'nextLine() hits the leftover', text:'nextLine() reads everything up to the next newline. The leftover \\n is immediately available -- so nextLine() returns an empty string without ever waiting for the user.'} }, { line:4, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:false,cls:false},{n:'name',v:'""',fresh:false,cls:false}],active:true}], custom:[], expl:{label:'Prints nothing', text:'name is "" -- an empty string. The program prints a blank line instead of a name. The user never got a chance to type.'} }, { line:5, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:false,cls:false},{n:'name',v:'""',fresh:false,cls:false}],active:true}], custom:[], expl:{label:'Bug summary', text:'The leftover \\n from nextInt() was silently consumed by nextLine(). The next scenario shows the one-line fix.'} }, ] }, { label: "CRLF fix", lines: [ { code: [['tok-kw','static '],['tok-kw','void '],['tok-meth','main'],['tok-op','(String[] args) {']] }, { code: [['',' '],['tok-type','Scanner '],['tok-op','sc = '],['tok-kw','new '],['tok-cls','Scanner'],['tok-op','(System.in);']] }, { code: [['',' '],['tok-type','int '],['tok-op','age = sc.'],['tok-meth','nextInt'],['tok-op','();']] }, { code: [['',' '],['tok-op','sc.'],['tok-meth','nextLine'],['tok-op','();'],['',' '],['tok-cmt','// flush leftover \\n']] }, { code: [['',' '],['tok-type','String '],['tok-op','name = sc.'],['tok-meth','nextLine'],['tok-op','();']] }, { code: [['',' '],['tok-op','System.out.'],['tok-meth','println'],['tok-op','('],['tok-str','"Name: "'],['tok-op',' + name);']] }, { code: [['tok-op','}']] }, ], steps: [ { line:0, stack:[{name:'main()',vars:[],active:true}], custom:[], expl:{label:'Program starts', text:'main() begins. Same goal as before: read an age, then a name on the next line.'} }, { line:1, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:true,cls:true}],active:true}], custom:[], expl:{label:'Scanner created', text:'new Scanner(System.in) is ready.'} }, { line:2, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:true,cls:false}],active:true}], custom:[{name:'Input buffer',tag:'leftover',rows:[{k:'buffered',v:'"\\n"',fresh:true}]}], expl:{label:'nextInt() runs', text:'User types 25 + Enter. nextInt() reads 25. The newline is left in the buffer.'} }, { line:3, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:false,cls:false}],active:true}], custom:[], expl:{label:'Flush the newline', text:'sc.nextLine() is called and its return value is discarded. It reads and throws away the leftover \\n. Buffer is now empty -- the drain is clear.'} }, { line:4, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:false,cls:false}],active:true}], custom:[{name:'Input buffer',tag:'has data',rows:[{k:'buffered',v:'"Alice\\n"',fresh:true}]}], expl:{label:'Waiting for real input', text:'nextLine() now waits because the buffer is empty. The user types Alice + Enter. The input arrives in the buffer.'} }, { line:4, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:false,cls:false},{n:'name',v:'"Alice"',fresh:true,cls:false}],active:true}], custom:[], expl:{label:'nextLine() reads correctly', text:'nextLine() reads "Alice" and consumes the newline as the line terminator. name is now "Alice".'} }, { line:5, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:false,cls:false},{n:'name',v:'"Alice"',fresh:false,cls:false}],active:true}], custom:[], expl:{label:'Prints correctly', text:'name is "Alice". The program prints "Name: Alice". The extra flush call was all that was needed.'} }, { line:6, stack:[{name:'main()',vars:[{n:'sc',v:'@Scanner1',fresh:false,cls:true},{n:'age',v:'25',fresh:false,cls:false},{n:'name',v:'"Alice"',fresh:false,cls:false}],active:true}], custom:[], expl:{label:'Done', text:'main() ends. Rule of thumb: always call sc.nextLine() once after nextInt() or next() before reading a full line with nextLine().'} }, ] } ] });