package Lisp::Subr::Core;

# implements the core subrs

use strict;
use vars qw($VERSION);

$VERSION = sprintf("%d.%02d", q$Revision: 1.8 $ =~ /(\d+)\.(\d+)/);

use Lisp::Symbol      qw(symbol);
use Lisp::Special     qw(make_special);
use Lisp::Reader      qw(lisp_read);
use Lisp::Printer     qw(lisp_print);
use Lisp::Interpreter qw(lisp_eval);

use Lisp::Cons        qw(consp);

my $lambda = symbol("lambda");
my $nil    = symbol("nil");
my $t      = symbol("t");

sub lisp_true { defined($_[0]) && $_[0] != $nil }

symbol("list")->function(sub {[@_]});

symbol("quote")->function(make_special(sub {$_[0]}));
symbol("set")->function(sub {$_[0]->value($_[1]); $_[1]} );
symbol("setq")->function(
   make_special(sub{my $val = lisp_eval($_[1]); $_[0]->value($val); $val}));

symbol("car")->function(sub {$_[0][0]});
symbol("cdr")->function(
sub {
   my $obj = shift;
   return $obj->[-1] if consp($obj);
   die "wrong-argument-type" unless ref($obj) eq "ARRAY";

   #XXX The semantics is not really correct in this situation, because
   # we will return a copy of the CDR.  This matters if somebody modifies
   # the original list or the CDR.
   [ @{$obj}[1 .. @$obj - 1] ];
});

symbol("print")->function(sub{lisp_print($_[0])});
symbol("read")->function(sub{lisp_read($_[0])});
symbol("eval")->function(sub{lisp_eval($_[0])});

# Just some way to print out something
symbol("write")->function(sub{print join("\n", (map lisp_print($_), @_), "")});

# control structues
symbol("progn")->function(sub {$_[-1]});
symbol("prog1")->function(sub {$_[0]});
symbol("prog2")->function(sub {$_[1]});

symbol("if")->function(
    make_special(
	sub {
	    my $cond = shift;
	    $cond = lisp_eval($cond);
	    if (lisp_true(lisp_eval($cond))) {
		return lisp_eval(shift);  # then
	    }
	    shift;  # skip then-form
	    my $res;
	    for (@_) { $res = lisp_eval($_) };
	    return $res;
	}));

symbol("cond")->function(
    make_special(
        sub {
	    my $res;
	    my $clause;
	    for $clause (@_) {
		$res = lisp_eval($clause->[0]);
		next unless lisp_true($res);
		my $pc;
		for ($pc = 1; $pc < @$clause; $pc++) {
		    $res = lisp_eval($clause->[$pc]);
		}
		return $res;
	    }
	    undef;
	}));


sub lisp_not { lisp_true($_[0]) ? $nil : $t }

symbol("not" )->function(\&lisp_not);
symbol("null")->function(\&lisp_not);

symbol("and")->function(
    make_special(
        sub {
	    my $res;
	    for (@_) {
		$res = lisp_eval($_);
		return $res unless lisp_true($res);
	    }
	    $res;
	}));

symbol("or")->function(
    make_special(
        sub {
	    my $res;
	    for (@_) {
		$res = lisp_eval($_);
		return $res if lisp_true($res);
	    }
	    $res;
	}));

symbol("while")->function(
    make_special(
        sub {
	    my $condition = shift;
	    while (lisp_true(lisp_eval($condition))) {
		# evaluate body
		for (@_) { lisp_eval($_) }
	    }
	    undef;
	}));

# numeric functions
symbol("floatp")->function(sub {$_[0] =~ /^[-+]?(?:\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?$/ ? $t : $nil });
symbol("integerp")->function(sub {$_[0] =~ /^\d+$/ ? $t : $nil });
symbol("numberp")->function(symbol("floatp")->function);
symbol("zerop")->function(sub {$_[0] == 0 ? $t : $nil });

symbol("=" )->function(sub {$_[0] == $_[1] ? $t : $nil });
symbol("/=")->function(sub {$_[0] != $_[1] ? $t : $nil });
symbol("<" )->function(sub {$_[0] <  $_[1] ? $t : $nil });
symbol("<=")->function(sub {$_[0] <= $_[1] ? $t : $nil });
symbol(">" )->function(sub {$_[0] >  $_[1] ? $t : $nil });
symbol(">=")->function(sub {$_[0] >= $_[1] ? $t : $nil });


symbol("1+")->function(sub { $_[0]+1} );
symbol("+")->function(sub { my $sum=shift; for (@_) {$sum+=$_} $sum });
symbol("1-")->function(sub { $_[0]-1} );
symbol("-")->function(
sub {
    return 0 if $_ == 0;
    return -$_[0] if @_ == 1;
    my $sum = shift; for(@_) {$sum-=$_}
    $sum
});
symbol("*")->function(sub { my $prod=1; for (@_){$prod*=$_} $prod});
symbol("/")->function(sub { my $div=shift; for (@_){ $div/=$_} $div});
symbol("%")->function(sub { $_[0] % $_[1]});

symbol("max")->function(sub {my $max=shift;for(@_){$max=$_ if $_ > $max}$max});
symbol("min")->function(sub {my $min=shift;for(@_){$min=$_ if $_ < $min}$min});


# defining functions
symbol("fset")->function(sub {$_[0]->function($_[1]); $_[1]});
symbol("symbol-function")->function(sub {$_[0]->function});

symbol("defun")->function(
    make_special(
        sub {
	    my $sym = shift;
	    $sym->function([$lambda, @_]);
	    $sym;
	}));

symbol("put")->function(sub{$_[0]->put($_[1] => $_[2])});
symbol("get")->function(sub{$_[0]->get($_[1])});


# dynamic scoping
symbol("let")->function(
    make_special(
        sub {
	    my $bindings = shift;
	    my @bindings = @$bindings;  # make a copy

	    # First evaluate all bindings as variables
	    for my $b (@bindings) {
		if (symbolp($b)) {
		    $b = [$b, $nil];
		} else {
		    my($sym, $val) = @$b;
		    $val = $val->value if $val && symbolp($val);
		    $b = [$sym, $val];
		}
	    }
   
	    # Then localize
	    require Lisp::Localize;
	    my $local = Lisp::Localize->new;
	    for my $b (@bindings) {
		$local->save_and_set(@$b);
	    }

	    my $res;
	    for (@_) {
		$res = lisp_eval($_);
	    }
	    $res;
	}));


symbol("let*")->function(
    make_special(
        sub {
	    my $bindings = shift;
	    require Lisp::Localize;
	    my $local = Lisp::Localize->new;

	    # Evaluate and localize in the order given
	    for my $b (@$bindings) {
		if (symbolp($b)) {
		    $local->save_and_set($b, $nil);
		} else {
		    my($sym, $val) = @$b;
		    $val = $val->value if $val && symbolp($val);
		    $local->save_and_set($sym, $val);
		}
	    }
	    my $res;
	    for (@_) {
		$res = lisp_eval($_);
	    }
	    $res;
	}));

1;