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().'} },
]
}
]
});