Acme-Chef

 view release on metacpan or  search on metacpan

lib/Acme/Chef.pm  view on Meta::CPAN

=head1 SYNOPSIS

  # Using the script that comes with the distribution.
  chef.pl file.chef
  
  # Using the module
  use Acme::Chef;
  
  my $compiled = Acme::Chef->compile($code_string);  
  print $compiled->execute();
  
  my $string = $compiled->dump(); # requires Data::Dumper
  # Save it to disk, send it over the web, whatever.
  my $reconstructed_object = eval $string;
  
  # or:
  $string = $compiled->dump('autorun'); # requires Data::Dumper
  # Save it to disk, send it over the web, whatever.
  my $output_of_chef_program = eval $string;

=head1 DESCRIPTION

Chef is an esoteric programming language in which programs look like
recipes. I needn't mention that using it in
production environment, heck, using it for anything but entertainment
ought to result in bugs and chaos in reverse order.

All methods provided by Acme::Chef are adequately described in the
synopsis. If you don't think so, you need to read the source code.

There has been an update to the Chef specification. I have implemented
the changes and marked them in the following documentation with
"I<new specification>".

With that out of the way, I would like to present a pod-formatted
copy of the Chef specification from David Morgan-Mar's homepage
(L<http://www.dangermouse.net/esoteric/chef.html>).

=head2 METHODS

This is a list of methods in this package.

=over 2

=item compile

Takes Chef source code as first argument and compiles a Chef program from it.
This method doesn't run the code, but returns a program object.

=cut

sub compile {
   my $proto = shift;
   my $class = ref $proto || $proto;

   my $code = shift;
   defined $code or croak "compile takes one argument: a code string.";

   my $self = {};

   bless $self => $class;

   my @paragraphs = $self->_get_paragraphs( $code );
   my @recipes    = $self->_paragraphsToRecipes(\@paragraphs);

   $_->compile() foreach @recipes;

   $self->{start_recipe} = $recipes[0]->recipe_name();

   $self->{recipes} = {
                        map { ($_->recipe_name(), $_) } @recipes
                      };

   return $self;
}


=item execute
 
Takes no arguments. Runs the program and returns its output.

=cut

sub execute {
   my $self = shift;

   my $start_recipe = $self->{recipes}->{ $self->{start_recipe} }->new();

   $start_recipe->execute($self->{recipes});

   return $start_recipe->output();   
}


=item dump
 
Takes one optional argument. If it equals 'autorun',
dump returns a string that, when evaluated, executes
the program and returns the output.

If the argument does not equal 'autorun', a different
string is returned that reconstructs the Acme::Chef
object.

=cut

sub dump {
   my $self = shift;
   my $type = shift;
   $type = '' if not defined $type;

   local $@ = undef;
   require Data::Dumper;

   my $dumper = Data::Dumper->new([$self], ['self']);
   $dumper->Indent(0);
   $dumper->Purity(1);

   my $dump = $dumper->Dump();

   if ($type =~ /^autorun$/) {
      $dump = 'do{my ' . $dump . ' bless $self => "' . (__PACKAGE__) . '"; $self->execute();} ';
   } else {
      $dump = 'do{my ' . $dump . ' bless $self => "' . (__PACKAGE__) . '";} ';
   }

   return $dump;
}


# private function _get_paragraphs

sub _get_paragraphs {
   my $self = shift;

   my $string = shift;

   $string =~ s/^\s+//;
   $string =~ s/\s+$//;

   return split /\n{2,}/, $string;
}


# private function _paragraphsToRecipes
# 
# Constructs recipes from an array ref of paragraphs.

sub _paragraphsToRecipes {
   my $self = shift;

   my $paragraphs = shift;
   $paragraphs = shift if not defined $paragraphs or not ref $paragraphs;

   while (chomp @$paragraphs) {}

   my @recipes;

   my $paragraph_no = 0;
   while (@$paragraphs) {
      my $recipe_name = shift @$paragraphs;
      $paragraph_no++;
      $recipe_name =~ /^[ ]*([\-\w][\- \w]*)\.[ ]*$/
        or croak "Invalid recipe name specifier in paragraph no. $paragraph_no.";
      $recipe_name = lc($1);

      last unless @$paragraphs;
      my $comments = shift @$paragraphs;
      $paragraph_no++;
      my $ingredients;
      if ( $comments =~ /^[ ]*Ingredients\.[ ]*\n/ ) {
         $ingredients = $comments;
         $comments = '';
      } else {
         last unless @$paragraphs;
         $ingredients = shift @$paragraphs;
         $paragraph_no++;
      }

      last unless @$paragraphs;
      my $cooking_time = shift @$paragraphs;
      $paragraph_no++;
      my $temperature;



( run in 2.046 seconds using v1.01-cache-2.11-cpan-437f7b0c052 )