Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??
My CUFP: Using perl to write an interactive perl tutorial.

Quite a while ago, in this node, I talked about the possibility of teaching perl to a couple of novice programmers.

ichimunki's response to my post was very enlightening... I agree that it'd be very useful to have a more interactive way for newbie programmers to just type stuff in and have it evaluated immediately. Here's my solution... a simple perl "shell" of sorts that accepts commands and evals them. The typed-in code is evaluated in its own separate package (WorkSpace), which allows me to do a couple neat tricks:

- Dumping of variables and subs. The users of my program can use the vars command to get a listing of the present states of all variables.

- An undo command. I save the state of the WorkSpace package's symbol table at the beginning of each call to my evaluate sub. These undo command pops a state off of the @undo_stack array and uses that to restore the state of the symbol table.

Eventually, I'd like to make the tutor more interactive by providing it with the ability to load and run tutorial scripts that interactively walk the user through the perl basics.

Most recent updates:
- The tutor now allows loading from and saving to files.
- The new display command displays the entire contents of what you've typed so far.
- The new edit command allows for editing your code in an editor of your choice. Defaults to notepad on a Win32 machine and your EDITOR environment variable (or vi) everywhere else.
- Fixed the undo/redo bug and allowed for essentially unlimited undo levels.
- Improved block mode that reads from Term::ReadLine instead of pure STDIN. Perhaps block mode isn't necessary anymore, though, since the edit command is now available.
- Fixed a bug whereby an empty line was interpreted as an error. Now, entering in nothing at a prompt simply spits out another prompt.

A couple minor bugs:
- edit has a race condition, which I am ignoring for now. Will fix it eventually. ;)
- save might produce non-compiling code, if the user has been entering in lines that don't end with semi-colons but should.

A sample run through the tutor:

Welcome to the Perl Tutor! Special commands are: (H)elp Get online help. (L)oad Load and evaluate the contents of the file. (V)ars Look at the present values of variables. (C)lear Clears the values of all variables and removes all undo da +ta. (B)lock Type in more than one line of code at a time. (U)ndo Undo last Perl command. (Q)uit Quit the Perl Tutor. Anything which is not a special command will be evaluated as Perl code +. > sub foo { $_ = shift; return $_ x 3 } > $bar = foo("42 "); > @baz = (1, 2, 3, 4); > %quux = @baz > vars $bar = "42 42 42 " @baz = (1, 2, 3, 4) sub foo { ... } %quux = (1 => 2, 3 => 4) > undo Successfully executed `undo' command. > vars $bar = "42 42 42 " @baz = (1, 2, 3, 4) sub foo { ... } > quit

And here, finally, is the code for the tutor:

#!/usr/bin/perl -w use strict; use Term::ReadLine; my $term = new Term::ReadLine 'Perl Tutor'; my $OUT = $term->OUT || *STDOUT; print "Welcome to the Perl Tutor!\n\n"; print_commands(); my @undo_stack; my @history; LINE: while (1) { $_ = $term->readline("> "); chomp; next LINE unless /./; help(), next LINE if /^h$/i || /^help/i; edit(), next LINE if /^e$/i || /^edit/i; vars(), next LINE if /^v$/i || /^vars/i; block(), next LINE if /^b$/i || /^block/i; load(), next LINE if /^l$/i || /^load/i; save(), next LINE if /^s$/i || /^save/i; clear(), next LINE if /^c$/i || /^clear/i; undo(), next LINE if /^u$/i || /^undo/i; re_do(), next LINE if /^r$/i || /^redo/i; display(), next LINE if /^d$/i || /^display/i; exit() if /^q$/i || /^quit/i; evaluate($_); } sub print_commands { print <<'DONE'; Special commands are: (H)elp Get online help. (V)ars Look at the present values of variables. (C)lear Clears the values of all variables and removes all undo da +ta. (B)lock Type in more than one line of code at a time. (U)ndo Undo last Perl command. (R)edo Redo the last `undo' command. (D)isplay Display the code you've entered so far. (L)oad Load and evaluate the contents of a file. (S)ave Save the code you've entered so far into a file. (E)dit Edit your code in the editor of your choice. (Q)uit Quit the Perl Tutor. Anything which is not a special command will be evaluated as Perl code +. DONE } sub load { my $filename = $term->readline("Please enter a filename to load: "); load_from_file($filename); } sub load_from_file { my $filename = shift; open (FILE, $filename) or return print "Could not load file $filenam +e: $!\n"; my @data = <FILE>; close(FILE) or return print "Could not close file $filename: $!\n"; my $data = join "", @data; chomp $data; clear(); print "File $filename loaded successfully.\n"; evaluate($data); } # This bit is insecure to race conditions. I'll try to fix that later, # but it shouldn't be a huge problem. sub edit { my $loaded = 0; my $filename; until ($loaded) { $filename = ".perl-tutor-$$-" . int(rand(2**16)) . ".tmp"; $loaded = 1 unless -e $filename; } my $default_editor = $ENV{EDITOR}; unless ($default_editor) { $default_editor = "vi"; $default_editor = "notepad.exe" if $^O =~ /Win32/i; } my $editor = $term->readline("Enter the name of an editor you would +like to use.\n[Default: $default_editor]: "); $editor = $editor || $default_editor; save_to_file($filename); system($editor, $filename); load_from_file($filename); unlink($filename) or return print "Could not remove temporary file $ +filename: $!\n"; } sub save { my $filename = $term->readline("Please enter a filename to save to: +"); save_to_file($filename); } sub save_to_file { my $filename = shift; open (FILE, ">$filename") or return print "Could not save to file $f +ilename: $!\n"; print FILE join("\n", @history); close(FILE) or return print "Could not close file $filename: $!\n"; } sub display { print join("\n", @history); print "\n"; } sub block { my @block = (); print "Entering Block Mode.\nEnter in as many lines of code as you w +ant; type `done' on a blank line when done.\n\n"; while (1) { $_ = $term->readline("block> "); chomp; last if /^done$/; push @block, $_; } evaluate(@block); } sub clear { %WorkSpace:: = (); @undo_stack = (); @history = (); print "Successfully cleared all variables and removed all undo data. +\n" } sub help { print "Eventually, a nice help message will go here.\n\n"; print_commands(); } sub quote_strings { my @return_val; foreach (@_) { my $scalar = $_; $scalar = "\"$_\"" unless $scalar =~ /^[0-9.]+$/; push @return_val, $scalar; } return @return_val if wantarray; return $return_val[0]; } sub undo { my @undo_data = @history; return print "Could not undo, because there is no undo data availabl +e.\n" unless @undo_data; push @undo_stack, \@undo_data; my @old_history = @history; pop @old_history; @history = (); %WorkSpace:: = (); evaluate(@old_history); } sub re_do { my $history_ref = pop @undo_stack; return print "Could not redo, because there is no redo data availabl +e.\n" unless $history_ref; my @new_history = @$history_ref; @history = (); %WorkSpace:: = (); evaluate(@new_history); } no strict; sub evaluate { package WorkSpace; eval(join ("", @_)); print "Unrecognized command or syntax error. (Type `help' for help.) +" if $@; push @history, @_ unless $@; print "\n"; package main; } sub vars { package WorkSpace; my $varcount = 0; foreach my $symname (sort keys %WorkSpace::) { local *sym = $WorkSpace::{$symname}; if (defined $sym) { $varcount++; my $scalar = main::quote_strings($sym); print "\$$symname = $scalar\n"; } if (defined @sym) { $varcount++; my @array = main::quote_strings(@sym); print "\@$symname = ("; print join(", ", @array); print ")\n"; } if (defined %sym) { $varcount++; print "\%$symname = ("; my $output; foreach my $key (sort keys %sym) { my $value = main::quote_strings($sym{$key}); $output .= "$key => $value, "; } chop $output; chop $output; print "$output)\n"; } if (defined &sym) { $varcount++; print "sub $symname { ... }\n"; } } print "No variables are currently defined.\n" unless $varcount; package main; }

In reply to Using perl to teach programming by Falkkin

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (3)
As of 2024-04-19 17:25 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found