# MPI.pm

=pod

=head1 NAME

Language::MPI - 2008.0217 Message Parsing Interpreter

=head1 SYNOPSIS

Processor for the Message Parsing Interpreter text
composition language, based on the MPI found in MU* online
environments, adapted for more general semantics.

http://en.wikipedia.org/wiki/Message_Parsing_Interpreter

=head1 USAGE

	use Language::MPI;
	$node = new Language::MPI($noderef);
	$node->setvar("varname", "varval");
	$results = $node->parse("tick {set:varname,{time:}} tock");
	$val = $node->readvar("varname");

MPI assumes an operating environment consisting of a set
of nodes each of which has a set of named properties.  How
these nodes and properties are stored and structured is up
to the application except that:

=over

=item * noderefs are perl scalars used by application
supplied functions.  Something with a printable value is
encouraged but not required.

=item * properties may be identified by and resolve to
plain text strings.

=back

MPI, in the interest of more general usage, expects some
support subroutines to be supplied by app to access nodes
and properties.  Should any of these not be supplied, errors
are trapped to prevent crashing.  Functions not needing
these should still work properly.  Should the application
designer wish, app data to be passed to these callbacks may
be set into and read from the object by the setvar() and
readvar() methods.

=over

=item mpi_neighbors($thisnode, $pattern, $obj)

$thisnode is a noderef.
$pattern is a string pattern used to specify which nodes
'neighboring' the current node are of interest.
returns list of noderefs;

=item mpi_prop($thisnode, $propname, $obj)

$propname is the string name of a property.
returns propval;

=item mpi_props($thisnode, $proppat, $obj)

$propat is a string specifier to a property directory or a
subset of properties.
returns list of propnames;

=item mpi_propset($thisnode, $propname, $val, $obj)

=back

=head1 INSTALATION

	perl Makefile.PL
	make
	make install

Or simply copy the MPI.pm file to Language/ under the perl
modules directory.  README and the man file for this package
exist as pod data in MPI.pm.

=head1 STATUS

Some MPI standard functions incomplete or unimplimented.  Testing incomplete.

=head1 Etc

This code developed using perl 5.8.8.  Might work with perl
5.6.0 or older with proper libraries.  Uses strict and warning.

Copyright (c)2007 Peter Hanely. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 LANGUAGE

=head2 VARS

=over

=item Variable names of alphabetic characters are general MPI use.

=item Names beginning with an underscore "_" are reserved
for mpi internal variables and should not be used by the
application.

=item Names beginning with "\" are suggested for application
values placed in the mpi object.

=cut

use strict;
use warnings;
# warning good for debug, but produce noise from good code
no warnings qw(uninitialized);
#use Carp;

package Language::MPI;

our ($VERSION, @ISA, @EXPORT_OK, $perl_list);
#use vars qw($VERSION, @ISA, @EXPORT_OK, $perl_list);

BEGIN
{
  require Exporter;
  our ($VERSION, @ISA, @EXPORT_OK);

  @ISA = qw(Exporter);
  @EXPORT_OK = qw(parse setvar readvar simp_functions);
  
  $VERSION = "2008.0217";
}

# control functions alter execution of their parameters,
# and thus parse their own parameters.
my %ctrl_functions =
(	'debug'	=> \&func_debug,
	'debugif'	=> \&func_debugif,
	'filter'	=> \&func_filter,
	'fold'		=> \&func_fold,
	'for'		=> \&func_for,
	'foreach'	=> \&func_foreach,
#	'func'	=> \&func_func,
	'if'		=> \&func_if,
	'lit'		=> \&func_lit,
	'lsort'		=> \&func_lsort,
	'neighbors'	=> \&func_neighbors,
	'parse'		=> \&func_parse,
	'while'	=> \&func_while,
	'with'	=> \&func_with
);

# simple functions have their parameters parsed by calling code.
my %simp_functions =
(	'abs'	=> \&func_abs,
	'add'	=> \&func_add,
	'and'	=> \&func_and,
	'attr'	=> \&func_attr,
	'convsecs'	=> \&func_convsecs,
	'convtime'	=> \&func_convtime,
	'count'	=> \&func_count,
	'date'	=> \&func_date,
	'debug'	=> \&func_debug,
	'debugif'	=> \&func_debugif,
	'dec'	=> \&func_dec,
	'default'	=> \&func_default,
	'delprop'	=> \&func_delprop,
	'dice'	=> \&func_dice,
	'dist'	=> \&func_dist,
	'div'	=> \&func_div,
	'eq'	=> \&func_eq,
	'escape'	=> \&func_escape,
	'eval'	=> \&func_eval,
	'exec'	=> \&func_exec,
	'filter'	=> \&func_filter,
	'fold'	=> \&func_fold,
	'for'	=> \&func_for,
	'foreach'	=> \&func_foreach,
	'ftime'	=> \&func_ftime,
	'fullname'	=> \&func_fullname,
	'func'	=> \&func_func,
	'ge'	=> \&func_ge,
	'gt'	=> \&func_gt,
	'if'	=> \&func_if,
	'inc'	=> \&func_inc,
	'index'	=> \&func_index,
	'instr'	=> \&func_instr,
	'isnum'	=> \&func_isnum,
	'lcommon'	=> \&func_lcommon,
	'le'	=> \&func_le,
	'list'	=> \&func_list,
	'listprops'	=> \&func_listprops,
	'lit'	=> \&func_lit,
	'lmember'	=> \&func_lmember,
	'lrand'	=> \&func_lrand,
	'lremove'	=> \&func_lremove,
	'lsort'	=> \&func_lsort,
	'lt'	=> \&func_lt,
	'ltimestr'	=> \&func_ltimestr,
	'lunion'	=> \&func_lunion,
	'lunique'	=> \&func_lunique,
	'max'	=> \&func_max,
	'midstr'	=> \&func_midstr,
	'min'	=> \&func_min,
	'mklist'	=> \&func_mklist,
	'mod'	=> \&func_mod,
	'mult'	=> \&func_mult,
	'name'	=> \&func_name,
	'ne'	=> \&func_ne,
	'neighbors2'	=> \&func_neighbors2,
	'nl'	=> \&func_nl,
	'not'	=> \&func_not,
	'null'	=> \&func_null,
	'or'	=> \&func_or,
	'parse'	=> \&func_parse,
	'prop'	=> \&func_prop,
	'rand'	=> \&func_rand,
	'secs'	=> \&func_secs,
	'select'	=> \&func_select,
	'set'	=> \&func_set,
	'sign'	=> \&func_sign,
	'smatch'	=> \&func_smatch,
	'stimestr'	=> \&func_stimestr,
	'store'	=> \&func_store,
	'strip'	=> \&func_strip,
	'strlen'	=> \&func_strlen,
	'sublist'	=> \&func_sublist,
	'subst'	=> \&func_subst,
	'subt'	=> \&func_subt,
	'time'	=> \&func_time,
	'timestr'	=> \&func_timestr,
	'timesub'	=> \&func_timesub,
	'tolower'	=> \&func_tolower,
	'toupper'	=> \&func_toupper,
	'tzoffset'	=> \&func_tzoffset,
	'v'	=> \&func_v,
	'version'	=> \&func_version,
	'while'	=> \&func_while,
	'with'	=> \&func_with,
	'xor'	=> \&func_xor
);

=head1 MPI primitives

=cut

=head2 {abs:num}

=cut

sub func_abs
{ my ($this, $val) = @_;
  abs $val->[0];
}

=head2 {add:num1,num2...}

=cut

sub func_add
{ my ($this, $val) = @_;
  my ($num, $tot);
  foreach $num (@$val)
  { $tot += $num; }
  $tot;
}

=head2 {and:num1,num2...}

=cut

sub func_and
{ my ($this, $val) = @_;
  my ($num, $tot);
  $tot = 1;
  foreach $num (@$val)
  { #$tot &&= $num;
    if (!$num) { $tot = 0; }
  }
  $tot;
}

=head2 {attr:attribute...,text}

=cut

sub func_attr
{ my ($this, $val) = @_;
  #stub
  $$val[-1];
}

sub func_convsecs
{ my ($this, $val) = @_;

}

sub func_convtime
{ my ($this, $val) = @_;

}

=head2 {count:array}

=cut

sub func_count
{ my ($this, $val) = @_;
  my (@arry);
  @arry = &unpack_list($val->[0]);
  return (scalar (@arry));
}

=head2 {date:}

=cut

sub func_date
{ my ($sec, $min, $hour, $mday, $mon, $year) = gmtime (time());
  if ($year < 1000) { $year += 1900; }
  $mon++;
  "$mon/$mday/$year";
}

sub func_debug
{ }

sub func_debugif
{ }

=head2 {dec:var,dec}

=cut

sub func_dec
{ my ($this, $val) = @_;
  my ($var, $inc) = @$val;
  $inc = $inc || 1;
  $this->{$var} -= $inc;
}

=head2 {default:var1,var2...}

=cut

sub func_default
{ my ($this, $val) = @_;
  my ($indx) = 0;
  while (($indx < @$val) && !($val->[$indx])) { $indx ++ }
  if ($indx < @$val) { $val->[$indx]; }
  else { ""; }
}

=head2 {delprop:var[,obj]}

=cut

sub func_delprop
{ my ($this, $val) = @_;
  my ($prop, $obj) = @$val;
  $obj = $obj || $this->{'_node'};
  if (defined &mpi_propset)
  { eval (&mpi_propset($obj, $prop, "", $this)); }
}

=head2 {dice:range[,count[,bonus]]}

=cut

sub func_dice
{ my ($this, $val) = @_;
  my ($range, $count, $bonus) = @$val;
  my ($indx, $tot);
  if ($count <= 0) { $count = 1; }
  for ($indx = 0; $indx < $count; $indx ++)
  { $tot += int(rand($range)+1); }
  $tot+$bonus;
}

=head2 {dist:x1,y2...}

=cut

sub func_dist
{ my ($this, $val) = @_;
  my ($x1, $y1, $z1, $x2, $y2, $z2) = @$val;
  if (@$val == 4)
  { ($x2, $y2) = ($z1, $x2); }
  my ($dx, $dy, $dz) = ($x2-$x1, $y2-$y1, $z2-$z1);
  sqrt($dx*$dx + $dy*$dy + $dz*$dz);
}

=head2 {div:num,num1...}

=cut

sub func_div
{ my ($this, $val) = @_;
  int($val->[0]/$val->[1]);
}

=head2 {eq:var1,var2}

=cut

sub func_eq
{ my ($this, $val) = @_;
  $val->[0] eq $val->[1];
}

sub func_escape
{ }

=head2 {eval:vars...}

=cut

sub func_eval
{ my ($this, $val) = @_;
  my ($tot, $param);
  foreach $param(@$val)
  { $tot .= &parse($this, $param); }
  $tot;
}

=head2 {exec:prop[,node]}

=cut

sub func_exec
{ my ($this, $val) = @_;
  my ($prop, $obj) = @$val;
  my ($tmp) = "";
  my ($propval) = eval {&mpi_prop($obj || $this->{'_node'}, $prop, $this)};
  if ($propval)
  { $tmp = &parse($this, $propval) || "" };
  $tmp;
}

sub func_filter
{ }

sub func_fold
{ }

=head2 {for:varname,start,end,increment,commands}

=cut

# control function, parses its own parameters
sub func_for
{ my ($this, $params) = @_;
  my ($varname,$start,$end,$increment,$command, $result, $results);
  ($varname, $params) = &parse_parameter($this, $params);
  ($start, $params) = &parse_parameter($this, $params);
  ($end, $params) = &parse_parameter($this, $params);
  ($increment, $command) = &parse_parameter($this, $params);
  $this->{$varname} = $start;
  if ($increment > 0)
  { while ($this->{$varname} <= $end)
    { ($result, $params) = &parse_parameters($this, $command);
      $results .= join '', @$result;
      $this->{$varname} += $increment;
    }
  }
  elsif ($increment < 0)
  { while ($this->{$varname} <= $end)
    { ($result, $params) = &parse_parameters($this, $command);
      $results .= join '', @$result;
      $this->{$varname} += $increment;
    }
  }
  else # sanity case
  { ($result, $params) = &parse_parameters($this, $command);
    $results .= join '', @$result;
  }
  ($results, $params);
}

=head2 {foreach:varname,list,command[,list seperator]}

=cut

sub func_foreach
{ my ($this, $params) = @_;
  my ($varname,$list,$expr,$sep, @list, $val, $res, $result);
  ($varname, $params) = &parse_parameter($this, $params);
  ($list, $expr) = &parse_parameter($this, $params);
  ($params) = &skip_param($this, $expr);
  #$sep = $sep || "\n";
  @list = unpack_list($list, $sep);
  foreach $val(@list)
  { $this->{$varname} = $val;
    ($res) = &parse_parameter($this, $expr);
    $result .= $res;
  }
  ($res, $params);
}

sub func_ftime
{ }

sub func_fullname
{ }

=head2 {func:name,var1:var2...,commands}

=cut

sub func_func
{ my ($this, $val) = @_;
  my ($func, $vars, $code) = @$val;
  $this->{"_f_$func"} = $code;
  $this->{"_f_$func v"} = $vars;
  "$func, $vars, $code";
}

=head2 {ge:var1,var2}

=cut

sub func_ge
{ my ($this, $val) = @_;
  $val->[0] >= $val->[1];
}

=head2 {gt:var1,var2}

=cut

sub func_gt
{ my ($this, $val) = @_;
  $val->[0] > $val->[1];
}

=head2 {if:condition,true[,false]}

=cut

sub func_if
{ my ($this, $params) = @_;
  my ($check, $ret);
  ($check, $params) = &parse_parameter($this, $params);
  if ($check)
  { ($ret, $params) = &parse_parameter($this, $params);
    ($params) = &skip_param($this, $params);
  }
  else
  { ($params) = &skip_param($this, $params);
    ($ret, $params) = &parse_parameter($this, $params);
  }
  $ret;
}

=head2 {inc:var,inc}

=cut

sub func_inc
{ my ($this, $val) = @_;
  my ($var, $inc) = @$val;
  $inc = $inc || 1;
  $this->{$var} += $inc;
}

=head2 {index:prop[,obj]}

=cut

sub func_index
{ my ($this, $val) = @_;
  my ($prop, $obj) = @$val;
  $obj = $obj || $this->{"_node"};
  $prop = eval {&mpi_prop($obj, $prop, $this)};
  if ($prop)
  { eval {&mpi_prop($obj, $prop, $this)} || ""; }
}

=head2 {insrt:string1,string2}

=cut

sub func_instr
{ my ($this, $val) = @_;
  my ($str1, $str2) = @$val;
  index($str1, $str2) + 1;
}

sub func_isnum
{ my ($this, $val) = @_;
  my ($num) = @$val;
  if (!$num) { $num = '0e0'; }
  $num;
}

=head2 {lcommon:list1,list2}

=cut

sub func_lcommon
{ my ($this, $val) = @_;
  my ($l1, $l2) = @$val;
  my (%h, $i, @res);
  foreach $i(&unpack_list($l1))
  { $h{$i} = 1; }
  foreach $i(&unpack_list($l2))
  { if ($h{$i})
    { push @res, $i;
      undef $h{$i}; # remove duplicates.
    }
  }
  &pack_list(@res);
}

=head2 {le:var1,var2}

=cut

sub func_le
{ my ($this, $val) = @_;
  $val->[0] <= $val->[1];
}

=head2 {list:props[,obj]}

=cut

sub func_list
{ my ($this, $val) = @_;
  my ($list, $obj) = @$val;
  my (@list, $i);
  $obj = $obj || $this->{"_node"};
  foreach $i(eval{&mpi_props($obj, $list, $this)})
  { push @list, eval(&mpi_prop($obj, $i, $this)); }
  &pack_list(@list);
}

=head2 {listprops:props[,obj]}

=cut

sub func_listprops
{ my ($this, $val) = @_;
  my ($list, $obj) = @$val;
  $obj = $obj || $this->{"_node"};
  &pack_list(eval{&mpi_props($obj, $list, $this)});
}

=head2 {lit:expression to not parse}

=cut

sub func_lit
{ my ($this, $param) = @_;
  my ($lit);
  ($param, $lit) = &skip_parameters($this, $param);
  $lit;
}

=head2 {lmember:list,item[,delimiter]}

=cut

sub func_lmember
{ my ($this, $val) = @_;
  my ($list, $item, $del) = @$val;
  my ($i, @list);
  @list = &unpack_list($list, $del);
  for ($i = 0; $i < @list && $list[$i] ne $item; $i++) { }
  if ($list[$i] eq $item) { return $i+1; }
  0;
}

=head2 {lrand:list[,delimiter]}

=cut

sub func_lrand
{ my ($this, $val) = @_;
  my ($list, $del) = @$val;
  my ($i, @list);
  @list = &unpack_list($list, $del);
  $list[int(rand @list)];
}

=head2 {lremove:list1,list2}

=cut

sub func_lremove
{ my ($this, $val) = @_;
  my ($l1, $l2) = @$val;
  my (%h, $i, @res);
  foreach $i(&unpack_list($l1))
  { $h{$i} = 1; }
  foreach $i(&unpack_list($l2))
  { if (!$h{$i})
    { push @res, $i;
      $h{$i} = 1; # remove duplicates.
    }
  }
  &pack_list(@res);
}

sub func_lsort
{ my ($this, $params) = @_;
  my ($list, @list, $var1, $var2, $code);
  ($list, $params) = &parse_parameter($this, $params);
  # do fancy sort later
  &pack_list(sort &unpack_list($list));
}

=head2 {lt:num1,num2}

=cut

sub func_lt
{ my ($this, $val) = @_;
  $val->[0] < $val->[1];
}

sub func_ltimestr
{ }

=head2 {lunion:list1,list2}

=cut

sub func_lunion
{ my ($this, $val) = @_;
  my ($l1, $l2) = @$val;
  my (%h, $i);
  foreach $i(&unpack_list($l1))
  { $h{$i} = 1; }
  foreach $i(&unpack_list($l2))
  { $h{$i} = 1; }
  &pack_list(keys %h);
}

=head2 {lunique:list}

=cut

sub func_lunique
{ my ($this, $val) = @_;
  my ($l1, $l2) = @$val;
  my (%h, $i, @res);
  foreach $i(&unpack_list($l1))
  { if (!$h{$i})
    { $h{$i} = 1;
      push @res,$i;
    }
  }
  &pack_list(@res);
}

=head2 {max:var1,var2...}

=cut

sub func_max
{ my ($this, $val) = @_;
  my ($tot, $var);
  $tot = $val->[0];
  foreach $var(@$val)
  { if ($tot > $var) { $tot = $var; } }
  $tot;
}

=head2 {midstr:string,start[,end]}

=cut

sub func_midstr
{ my ($this, $val) = @_;
  my ($str, $pos1, $pos2);
  substr ($str, $pos1, $pos2);
}

=head2 {min:var1,var2...}

=cut

sub func_min
{ my ($this, $val) = @_;
  my ($tot, $var);
  $tot = $val->[0];
  foreach $var(@$val)
  { if ($tot > $var) { $tot = $var; } }
  $tot;
}

=head2 {mklist:list items}

=cut

sub func_mklist
{ my ($this, $val) = @_;
  #join "\n", @$val;
  &pack_list(&unpack_list($val));
}

=head2 {mod:num1,num2}

=cut

sub func_mod
{ my ($this, $val) = @_;
  $val->[0] % $val->[1];
}

=head2 {mult:num1,num2...}

=cut

sub func_mult
{ my ($this, $val) = @_;
  my ($num, $tot);
  $tot = 1;
  foreach $num (@$val)
  { $tot *= $num; }
  $tot;
}

sub func_name
{ }

=head2 {ne:var1,var2}

=cut

sub func_ne
{ my ($this, $val) = @_;
  $val->[0] ne $val->[1];
}

=head2 {neighbors:varname,pattern,code}

=cut

sub func_neighbors
{ my ($this, $params) = @_;
  my ($varname,$pattern,$expr, @list, $val, $res, $result);
  ($varname, $params) = &parse_parameter($this, $params);
  ($pattern, $expr) = &parse_parameter($this, $params);
  @list = eval {&mpi_neighbors($this->{'_node'}, $pattern, $this)};
  foreach $val(@list)
  { $this->{$varname} = $val;
    ($res, $params) = &parse_parameter($this, $expr);
    $result .= $res;
  }
  if (@list == 0)
  { $res = "";
    $params = &skip_parameters($this, $expr);
    $params =~ /^\}(.*)/;
    $params = $! || $params;
  }
  ($res, $params);
}

=head2 {neighbors2:pattern}

=cut

sub func_neighbors2
{ my ($this, $params) = @_;
  my ($pattern) = @$params;
  &pack_list(eval {&mpi_neighbors($this->{'_node'}, $pattern, $this)});
}


=head2 {nl:}

=cut

sub func_nl
{ "\n"; }

=head2 {not:var}

=cut

sub func_not
{ my ($this, $val) = @_;
  !($val->[0]);
}

=head2 {null:...}

=cut

sub func_null
{ ""; }

=head2 {or:var1,var2...}

=cut

sub func_or
{ my ($this, $val) = @_;
  my ($num, $tot);
  foreach $num (@$val)
  { #$tot ||= $num;
    if (!$num) { $tot = 0; }
  }
  $tot;
}

sub func_parse
{ }

=head2 {prop:property,node}

=cut

sub func_prop
{ my ($this, $val) = @_;
  my ($prop, $obj) = @$val;
  $obj = $obj || $this->{"_node"};
  eval {&mpi_prop($obj, $prop, $this)} || "";
}

=head2 {rand:props[,obj]}

=cut

sub func_rand
{ my ($this, $val) = @_;
  my ($list, $obj) = @$val;
  my (@list, $i);
  $obj = $obj || $this->{"_node"};
  @list = eval{&mpi_props($obj, $list, $this)};
  eval(&mpi_prop($obj, $list[int(rand @list)], $this));
}

=head2 {secs:}

=cut

sub func_secs
{ time(); }

sub func_select
{ }

=head2 {set:var,val}

=cut

sub func_set
{ my ($this, $val) = @_;
  my ($var, $v) = @$val;
  if ($var =~ /^[a..zA..Z]/) # some vars are reserved for engine use
  { $this->{$var} = $v; }
}

=head2 {sign:num}

=cut

sub func_sign
{ my ($this, $val) = @_;
  $val->[0] <=> 0;
}

=head2 {smatch:string,pattern}

=cut

sub func_smatch
{ my ($this, $val) = @_;
  my ($str, $pat) = @$val;
  $str =~ /($pat)/;
  $1
}

sub func_stimestr
{ }

=head2 {store:val,property[,node]}

=cut

sub func_store
{ my ($this, $val) = @_;
  my ($str, $prop, $obj) = @$val;
  $obj = $obj || $this->{'_node'};
  eval {&mpi_propset($obj, $prop, $str, $this)} || "";
}

=head2 {strip:string}

=cut

sub func_strip
{ my ($this, $val) = @_;
  chomp $val->[0];
  $val->[0] =~ s/^\s*//;
  $val->[0] =~ s/\s*$//;
  $val->[0];
}

=head2 {strlen:string}

=cut

sub func_strlen
{ my ($this, $val) = @_;
  length $val->[0];
}

=head2 {sublist:list,pos1,pos2[,sep]}

=cut

sub func_sublist
{ my ($this, $val) = @_;
  my ($list, $pos1, $pos2, $sep) = @$val;
  my @list = &unpack_list($list, $sep);
  if (!defined($pos2)) { $pos2 = @list; }
  &pack_list( splice( @list, $pos1+1, $pos2-$pos1) );
}

=head2 {subst:string,old,new}

=cut

sub func_subst
{ my ($this, $val) = @_;
  my ($str, $old, $new) = @$val;
  $str =~ s/$old/$new/g;
  $str;
}

=head2 {subt:num1,num2...}

=cut

sub func_subt
{ my ($this, $val) = @_;
  my ($num, $tot);
  $tot = shift @$val;
  foreach $num (@$val)
  { $tot -= $num; }
  $tot;
}

=head2 {time:}

=cut

sub func_time
{ my ($sec, $min, $hour, $mday, $mon, $year) = gmtime(time());
  if ($year < 1000) { $year += 1900; }
  "$hour:$min:$sec";
}

sub func_timestr
{ }

sub func_timesub
{ }

=head2 {tolower:string}

=cut

sub func_tolower
{ my ($this, $val) = @_;
  lc $val->[0];
}

=head2 {toupper:string}

=cut

sub func_toupper
{ my ($this, $val) = @_;
  uc $val->[0];
}

sub func_tzoffset
{ }

=head2 {v:varname}

=cut

sub func_v
{ my ($this, $val) = @_;
  $this->{$val->[0]};
}

=head2 {version:}

=cut

sub func_version
{ $VERSION; }

=head2 {while:condition,command}

=cut

sub chk_cond
{ my ($this, $cond) = @_;
  my ($res) = &parse_parameter($this, $cond);
# debug
#  print "cond $res -- ";
  $res;
}

sub func_while
{ my ($this, $params) = @_;
  my ($go,$cond,$expr,$sep, $val, $res, $result, %save, $maxloop);
  $cond = $params;
  ($expr) = &skip_param($this, $params);
  ($params) = &skip_param($this, $expr);
  $maxloop = 255; #sanity
  while (&chk_cond($this, $cond) && ($maxloop >= 0))
  { ($res, $params) = &parse_parameter($this, $expr); 
    $result .= $res;
    $maxloop --;
  }
  ($result, $params);
}

=head2 {with:varname...}

=cut

sub func_with
{ my ($this, $params) = @_;
  my ($varname,$expr,$val, $res, %save);
  ($varname, $expr) = &parse_parameter($this, $params);
  foreach $val(split /:/, $varname)
  { $save{$val} = $this->{$val};
    $this->{$val} = ''; # a 'null' that isn't undef
  }
  ($res, $params) = &parse_parameter($this, $expr);
  foreach $val(split /:/, $varname)
  { $this->{$val} = $save{$val}; }
  ($res, $params);
}

=head2 {xor:num1,num2...}

=cut

sub func_xor
{ my ($this, $val) = @_;
  my ($num, $tot);
  $tot = shift @$val;
  foreach $num (@$val)
  { $tot = ($tot xor $num); }
  $tot;
}

# ====================================================
# core routines
# ====================================================
=head2 -

=cut 

=head1 Public object methods

=cut

=head2 new(noderef);

Create new MPI object.

=cut

sub new
{ my ($class, $node) = @_;
  my (%this);
  $this{'_node'} = $node;
  bless \%this, $class;
}

=head2 $mpi->setvar(var,val);

Sets a variable in the mpi object to a scalar value.

=cut

sub setvar
{ my ($this, $var, $val) = @_;
  $this->{$var} = $val;
}

=head2 $mpi->readvar(var);

Reads a scalar value from the mpi object

=cut

sub readvar
{ my ($this, $var) = @_;
  $this->{$var};
}

# unpack a list in either MPI \n delimited string or perl list ref
sub unpack_list
{ my ($list, $sep) = @_;
  my (@list);
  if (ref $list)
  { @list = @$list; }
  else
  { $sep = $sep || "\n";
    @list = split "\n", $list;
  }
  @list;
}

sub pack_list
{ if ($perl_list) {return \@_}
  else
  { join "\n", @_; }
}

# parse 1 parameter, which may contain a mix of plain text and MPI functions
sub parse_parameter
{ my ($this, $text) = @_;
  my ($result, $prefix, $remainder, $match, $value);
  $result = "";
  # find start of MPI function or terminating comma
  while ($text =~ /(,|\}|\{\w+:?)/ )
  { $match = $1;
    # terminating comma or '}', split remaining text into result and remainder
    if ($match =~ /(,|\})/)
    { ($prefix, $remainder) = split $match, $text, 2;
      $result .= $prefix;
      return ($result, $remainder, $match);
    }
    # mpi function, evaluate
    elsif ($match =~ /\{(\w+)/ )
    { ($prefix, $remainder) = split $match, $text, 2;
      $result .= $prefix;
      ($value, $remainder) = &eval_mpi($this, $1, $remainder);
#if (! defined($value))
#{ "catch"; }
      $result .= $value;
      $text = $remainder;
    }
  }
  # nothing left to parse
  ($result.$text, '', '');
}

# skip a parameter
sub skip_param
{ my ($this, $text) = @_;
  my ($match, $prefix, $remainder);
  while ($text =~ /(,|\}|\{\w+:?)/ )
  { $match = $1;
    # terminating comma or }, split remaining text into result and remainder
    if ($match =~ /([,\}])/)
    { ($prefix, $remainder) = split $1, $text, 2;
      return ($remainder, $match);
    }
    # mpi function, recurse in and skip
    elsif ($match =~ /\{(\w+)/ )
    { ($prefix, $remainder) = split $match, $text, 2;
      ($remainder) = &skip_parameters($this, $remainder);
      $text = $remainder;
    }
  }
  # nothing left to parse
  ('');
}

# skip all remaining parameters
sub skip_parameters
{ my ($this, $text) = @_;
  my ($match, $prefix, $prefix1, $remainder);
  while ($text =~ /(\}|\{\w+:?)/ )
  { $match = $1;
    # terminating }, split remaining text into result and remainder
    if ($match =~ /([\}])/)
    { ($prefix, $remainder) = split $1, $text, 2;
      $prefix1 .= $prefix;
      return ($remainder, $prefix1, $1);
    }
    # mpi function, recurse in and skip
    elsif ($match =~ /\{(\w+)/ )
    { ($prefix, $remainder) = split $match, $text, 2;
      $prefix1 .= $prefix.$match;
      ($remainder, $prefix, $match) = &skip_parameters($this, $remainder);
      $prefix1 .= $prefix.$match;
      $text = $remainder;
    }
  }
  # nothing left to parse
  ('');
}

# parse all parameters for the current function
sub parse_parameters
{ my ($this, $text) = @_;
  my @params;
  my ($result, $term);
  $term = "zz";
  while ($term =~ /[^\}]/)
  { ($result, $text, $term) = &parse_parameter($this, $text);
    push @params, $result;
  }
  (\@params, $text);
}
 
# evaluate 1 MPI function
sub eval_mpi
{ my ($this, $function, $text) = @_;
  my ($result, $remainder, $params);
  $function = lc $function;
  $result = "";
  # if function is in control function list, pass raw text and let function parse.
  if ($ctrl_functions{$function})
  { ($result, $remainder) = &{$ctrl_functions{$function}}($this, $text);
  }
  # parse parameters and pass results to function.
  elsif ($simp_functions{$function})
  { ($params, $remainder) = &parse_parameters($this, $text);
    ($result) = &{$simp_functions{$function}}($this, $params);
  }
  # else concat parameters
  elsif ($this->{"_f_$function"})
  { my (@vars, $var, $i, %save);
    ($params, $remainder) = &parse_parameters($this, $text);
    @vars = split /:/, $this->{"_f_$function v"};
    for ($i = 0; $i < @vars; $i++)
    { $var = $vars[$i];
      $save{$var} = $this->{$var};
      $this->{$var} = $params->[$i];
    }
    $result = &parse($this, $this->{"_f_$function"});
    foreach $var(split /:/, $this->{"_f_$function v"})
    { $this->{$var} = $save{$var}; }
  }
  else
  { ($params, $remainder) = &parse_parameters($this, $text);
    $result = join (',', @$params);
  }
  ($result, $remainder);
}

=head2 $mpi->parse(string);

Processes a string for MPI codes

=cut

# parse a text block.  simular to parse_parameter, except not terminating at ','
sub parse
{ my ($this, $text) = @_;
  my ($result, $value, $term);
  # while we have unprocessed text
  #   find MPI, if any.
  #   preceeding text copied to result.
  #   MPI evaluated and retuned values added to result.

  $term = "zz"; # meaningless except not null
  while ($term)
  { ($value, $text, $term) = &parse_parameter($this, $text);
    $result .= $value.$term;
  }

  $result;
}

1;

__END__

use Language::MPI;

# dummy test callbacks
package Language::MPI;

sub mpi_neighbors 
{ my ($thisnode, $pattern) = @_;
  "neighbors:$thisnode,$pattern";
}
sub mpi_prop
{ my ($thisnode, $propname) = @_;
  "$thisnode/$propname";
}

sub mpi_props
{ my ($thisnode, $proppat) = @_;
  ("propa", "propb", "propc");
}

sub mpi_propset
{ my ($thisnode, $propname, $val) = @_;
  "$thisnode,$propname,$val";
}

package main;

$mpi = new Language::MPI('dummy node');

@tests =
( 'plain text, no MPI',
  '{toupper:lower to upper}',
  '1+2 = {add:1,2} = 2+1',
  '{tolower:{toupper:lower to upper to lower}}',
  '{for:i,1,4,1,{v:i} }',
  
  '{abs:-2}',
  '{add:1,2,3}',
  '{and:2,4,6}',
  '{attr:attribute...,text}',
  '{mklist:list,items}',
  '{set:list,{mklist:list,items}}',
  '{count:{v:list}',
  '{date:}',
  '{set:var,1}',
  '{v:var}',
  '{dec:var,2}',
  '{inc:var,4}',
  '{default:1,2}',
  '{dice:6,3,2}',
  '{dist:3,4}',
  '{div:81,9,3}',
  '{eq:var1,var1}',
  '{eval:vars...}',
  '{foreach:var,{v:list},:{v:var}:}',
  '{ge:2,2}',
  '{gt:2,1}',
  '{if:true,true statement,false statement}',
  '{insrt:string1,ing}',
  '{lcommon:{v:list},{mklist:items}}',
  '{le:1,2}',
  '{lmember:{v:list},items}',
  '{lit:{a:dummy,mpi}}',
  '{max:1,2,3}',
  '{min:1,2,3}',
  '{mod:9,4}',
  '{mult:2,4,8}',
  '{ne:var1,var2}',
  '::{nl:}::',
  '{not:true}',
  '{null:a big statement to execute but not keep a value from...}',
  '{or:1,2,0}',
  '{secs:}',
  '{sign:-100}',
  '{smatch:string,ing}',
  '{strip:  string   }',
  '{strlen:string}',
  '{subst:string,ing,ung}',
  '{subt:100,50,25}',
  '{time:}',
  '{version:}',
  '{set:var,3}',
  '{while:{v:var},{v:var}>>{dec:var,1}:}',
  '{with:var,{v:var}}',
  '{xor:1,1,1}',
  'The following are dummy without support functions',
  '{delprop:var,obj}',
  '{exec:prop,node}',
  '{index:prop,obj}',
  '{list:props,obj}',
  '{listprops:props,obj}',
  '{neighbors:varname,pattern,{v:varname}}',
  '{prop:property,node}',
  '{store:val,property,node}',
);

foreach $test (@tests)
{ print "test: $test\n";
  $result = $mpi->parse($test);
  print "result: [[$result]]\n\n";
}

#Makefile.pl
use ExtUtils::MakeMaker;

WriteMakefile
( NAME		=> 'Language::MPI',
  VERSION_FROM	=> 'lib/Language/MPI.pm'
);

__END__

MANIFEST
Makefile.pl
README
lib/Language/MPI.pm

__END__

additional notes:

perldoc ExtUtils::MakeMaker::Tutorial



#abs          add         and          attr        convsecs     convtime
#count        date        debug        debugif     dec          default      
#delprop      dice        dist         div         eq           escape       
#eval         exec        filter       fold
#ftime        fullname    ge          gt           
#inc          index       instr        isnum       lcommon      le          
#list         listprops   lmember     lrand        lremove     
#lsort        lt          ltimestr     lunion      lunique      max          
#midstr       min         mklist       mod         mult         name        
#ne           nl          not          null        or           parse        
#prop         rand        secs         select      set          sign  
#smatch       stimestr    store        strip       strlen       sublist  
#subst        subt        time         timestr     timesub      tolower     
#toupper      tzoffset    v            version     xor
