#!/usr/bin/perl
use 5.016;
use warnings;

sub factorial { $_[0] < 1 ? $_[1] : factorial($_[0] - 1, $_[1] * $_[0]) }

sub rest { [ @{$_[0]}[1 .. $#{$_[0]}] ] }

sub term {
	my ($t, $args) = @_;

	given($t->[0]){
		when('log('){
			return sub {
				my ($t, $x) = @_;
				($t->[0] eq ')') or die;
				return (rest($t), log($x));
			}->(expr(rest($t), $args));
		}
		when('sqrt('){
			return sub {
				my ($t, $x) = @_;
				($t->[0] eq ')') or die;
				return (rest($t), sqrt($x));
			}->(expr(rest($t), $args));
		}
		when(/[0-9]+/){
			return (rest($t), $t->[0]);
		}
		when(/[a-z]+/){
			return (rest($t), $args->{$t->[0]});
		}
	}

	die;
}

sub fact {
	my ($t, $args) = @_;

	return sub{
		my ($t, $x) = @_;
		return (($t->[0] eq '!') ? (rest($t), factorial($x, 1)) : ($t, $x));
	}->(term($t, $args));
}

sub pow {
	my ($t, $args) = @_;

	return sub{
		my ($t, $x) = @_;
		return (
			($t->[0] eq '^') ?
				sub{ $_[0], $x ** $_[1] }->(fact(rest($t), $args)) :
				($t, $x)
		);
	}->(fact($t, $args));
}

sub expr {
	my ($t, $args) = @_;

	return sub{
		my ($t, $x) = @_;
		return (
			($t->[0] eq '*') ?
				sub{ $_[0], $x * $_[1] }->(pow(rest($t), $args)) :
				($t, $x)
		);
	}->(pow($t, $args));
}

sub interpret {
	my ($code, $args) = @_;

	my $t = [ $code =~ m/\G\s*([0-9]+|[\!\)\^\*]|log\(|sqrt\(|[a-z]+)/g, '' ];

	return (expr($t, $args))[1];
}


my @code = split(/\n/, <<'EOF');
2^n
2^log(n)
4^n
n
n^2
n!
n*log(n)
log(n!)
log(log(n))
sqrt(log(n))
EOF

my $args = {n => 10};

say map{ "$_->[0] = $_->[1]\n" }
	sort{ $b->[1] <=> $a->[1] }
	map{ [ $_ => interpret($_, $args) ] } @code;
