diff --git a/lib/DDG/Goodie/Calculator.pm b/lib/DDG/Goodie/Calculator.pm index 4c9de76da00..1aabdb1dbee 100644 --- a/lib/DDG/Goodie/Calculator.pm +++ b/lib/DDG/Goodie/Calculator.pm @@ -1,6 +1,8 @@ package DDG::Goodie::Calculator; # ABSTRACT: do simple arthimetical calculations +use feature 'state'; + use DDG::Goodie; use List::Util qw( all first max ); @@ -21,27 +23,6 @@ attribution github => ['https://github.com/duckduckgo', 'duckduckgo'], twitter => ['http://twitter.com/duckduckgo', 'duckduckgo']; -triggers query_nowhitespace => qr< - ^ - ( what is | calculate | solve | math )? !? - - [\( \) x X * % + / \^ \$ -]* - - (?: [0-9 \. ,]* ) - (?: gross | dozen | pi | e | c |) - [\( \) x X * % + / \^ 0-9 \. , \$ -]* - - (?(1) (?: -? [0-9 \. ,]+ |) |) - (?: [\( \) x X * % + / \^ \$ -] | times | divided by | plus | minus | cos | sin | tan | cotan | log | ln | log[_]?\d{1,3} | exp | tanh | sec | csc)+ - - (?: [0-9 \. ,]* ) - (?: gross | dozen | pi | e | c |) - - [\( \) x X * % + / \^ 0-9 \. , \$ -]* =? - - $ - >xi; - # This is probably YAGNI territory, but since I have to reference it in two places # and there are a multitude of other notation systems (although some break the # 'thousands' assumption) I am going to pretend that I do need it. @@ -74,7 +55,7 @@ foreach my $style (@known_styles) { # Luckily it will someday be able to be tokenized so this won't apply. my $all_seps = join('', map { $_->{decimal} . $_->{thousands} } @known_styles); -my $numbery = qr/^[\d$all_seps]+$/; +my $numbery = qr/[\d$all_seps]+/; my $funcy = qr/[[a-z]+\(|log[_]?\d{1,3}\(|\^/; # Stuff that looks like functions. my %named_operations = ( @@ -85,8 +66,11 @@ my %named_operations = ( 'plus' => '+', 'divided\sby' => '/', 'ln' => 'log', # perl log() is natural log. + 'squared' => '**2', ); +my $ored_operations = join('|', keys %named_operations); + my %named_constants = ( dozen => 12, e => 2.71828182845904523536028747135266249, # This should be computed. @@ -96,12 +80,19 @@ my %named_constants = ( my $ored_constants = join('|', keys %named_constants); # For later substitutions +my $extra_trigger_words = qr/^(?:whatis|calculate|solve|math)/; +triggers query_nowhitespace => qr< + $extra_trigger_words? + (\s|$funcy|$ored_constants|$ored_operations|$numbery)* + $ + >xi; + handle query_nowhitespace => sub { my $results_html; my $results_no_html; my $query = $_; - $query =~ s/^(?:whatis|calculate|solve|math)//; + $query =~ s/$extra_trigger_words//; if ($query !~ /[xX]\s*[\*\%\+\-\/\^]/ && $query !~ /^-?[\d]{2,3}\.\d+,\s?-?[\d]{2,3}\.\d+$/) { my $tmp_result = ''; @@ -125,7 +116,7 @@ handle query_nowhitespace => sub { $tmp_expr =~ s#\b$name\b# $constant #ig; } - my @numbers = grep { $_ =~ $numbery } (split /\s+/, $tmp_expr); + my @numbers = grep { $_ =~ /^$numbery$/ } (split /\s+/, $tmp_expr); my $style = display_style(@numbers); return unless $style; @@ -137,6 +128,7 @@ handle query_nowhitespace => sub { # e.g. sin(100000)/100000 completely makes this go haywire. alarm(1); $tmp_result = eval($tmp_expr); + alarm(0); # Assume the string processing will be "fast enough" }; # Guard against non-result results @@ -165,8 +157,7 @@ handle query_nowhitespace => sub { $results_no_html = $results_html = $tmp_q; # Superscript (before spacing). - $results_html =~ s/\^([^\)]+)/$1<\/sup>/g; - $results_html =~ s/\^(\d+|\b(?:$ored_constants)\b)/$1<\/sup>/g; + $results_html =~ s/\^($numbery|\b$ored_constants\b)/$1<\/sup>/g; ($results_no_html, $results_html) = map { spacing($_) } ($results_no_html, $results_html); return if $results_no_html =~ /^\s/; @@ -176,18 +167,29 @@ handle query_nowhitespace => sub { # Now add = back. $results_no_html .= ' = '; - $results_html .= ' = '; - $results_html = - qq(
$results_html$tmp_result
); return $results_no_html . $tmp_result, - html => $results_html, + html => wrap_html($results_html, $tmp_result), heading => "Calculator"; } return; }; +# Add some HTML and styling to our output +# so that we can make it prettier (unabashedly stolen from +# the ReverseComplement goodie.) +sub append_css { + my $html = shift; + state $css = share("style.css")->slurp; + return "$html"; +} + +sub wrap_html { + my ($entered, $result) = @_; + return append_css("
$entered = $result
"); +} + #separates symbols with a space #spacing '1+1' -> '1 + 1' sub spacing { @@ -195,7 +197,7 @@ sub spacing { $text =~ s/(\s*(? test_zci( "2 - 2 = 0", heading => 'Calculator', - html => qq(
2 - 2 = 0
) + html => qr/./, ), 'solve 2+2' => test_zci( "2 + 2 = 4", heading => 'Calculator', - html => qq(
2 + 2 = 4
) + html => qr/./, ), '2^8' => test_zci( "2 ^ 8 = 256", heading => 'Calculator', - html => qq(
28 = 256
) + html => qr/./, ), '2 *7' => test_zci( "2 * 7 = 14", heading => 'Calculator', - html => qq(
2 * 7 = 14
) + html => qr/./, ), '1 dozen * 2' => test_zci( "1 dozen * 2 = 24", heading => 'Calculator', - html => qq(
1 dozen * 2 = 24
) + html => qr/./, ), 'dozen + dozen' => test_zci( "dozen + dozen = 24", heading => 'Calculator', - html => qq(
dozen + dozen = 24
) + html => qr/./, ), '2divided by 4' => test_zci( "2 divided by 4 = 0.5", heading => 'Calculator', - html => qq(
2 divided by 4 = 0.5
) + html => qr/./, ), '2^dozen' => test_zci( "2 ^ dozen = 4,096", heading => 'Calculator', - html => qq(
2dozen = 4,096
) + html => qr/./, ), '2^2' => test_zci( "2 ^ 2 = 4", heading => 'Calculator', - html => qq(
22 = 4
) + html => qr/./, ), '2^0.2' => test_zci( "2 ^ 0.2 = 1.14869835499704", heading => 'Calculator', - html => - qq(
20.2 = 1.14869835499704
) + html => qr/./, ), 'cos(0)' => test_zci( "cos(0) = 1", heading => 'Calculator', - html => qq(
cos(0) = 1
) + html => qr/./, ), 'tan(1)' => test_zci( "tan(1) = 1.5574077246549", heading => 'Calculator', - html => - qq(
tan(1) = 1.5574077246549
) + html => qr/./, ), 'tanh(1)' => test_zci( "tanh(1) = 0.761594155955765", heading => 'Calculator', - html => - qq(
tanh(1) = 0.761594155955765
) + html => qr/./, ), 'cotan(1)' => test_zci( "cotan(1) = 0.642092615934331", heading => 'Calculator', - html => - qq(
cotan(1) = 0.642092615934331
) + html => qr/./, ), 'sin(1)' => test_zci( "sin(1) = 0.841470984807897", heading => 'Calculator', - html => - qq(
sin(1) = 0.841470984807897
) + html => qr/./, ), 'csc(1)' => test_zci( "csc(1) = 1.18839510577812", heading => 'Calculator', - html => - qq(
csc(1) = 1.18839510577812
) + html => qr/./, ), 'sec(1)' => test_zci( "sec(1) = 1.85081571768093", heading => 'Calculator', - html => - qq(
sec(1) = 1.85081571768093
) + html => qr/./, ), 'log(3)' => test_zci( "log(3) = 1.09861228866811", heading => 'Calculator', - html => - qq(
log(3) = 1.09861228866811
) + html => qr/./, ), 'ln(3)' => test_zci( "ln(3) = 1.09861228866811", heading => 'Calculator', - html => - qq(
ln(3) = 1.09861228866811
) + html => qr/./, ), 'log10(100.00)' => test_zci( "log10(100.00) = 2", heading => 'Calculator', - html => qq(
log10(100.00) = 2
) + html => qr/./, ), 'log_10(100.00)' => test_zci( "log_10(100.00) = 2", heading => 'Calculator', - html => qq(
log_10(100.00) = 2
) + html => qr/./, ), 'log_2(16)' => test_zci( "log_2(16) = 4", heading => 'Calculator', - html => qq(
log_2(16) = 4
) + html => qr/./, ), 'log_23(25)' => test_zci( "log_23(25) = 1.0265928122321", heading => 'Calculator', - html => - qq(
log_23(25) = 1.0265928122321
) + html => qr/./, ), 'log23(25)' => test_zci( "log23(25) = 1.0265928122321", heading => 'Calculator', - html => - qq(
log23(25) = 1.0265928122321
) + html => qr/./, ), '$3.43+$34.45' => test_zci( '$3.43 + $34.45 = $37.88', heading => 'Calculator', - html => qq(
\$3.43 + \$34.45 = \$37.88
) + html => qr/./, ), '$3.45+$34.45' => test_zci( '$3.45 + $34.45 = $37.90', heading => 'Calculator', - html => qq(
\$3.45 + \$34.45 = \$37.90
) + html => qr/./, ), '$3+$34' => test_zci( '$3 + $34 = $37', heading => 'Calculator', - html => qq(
\$3 + \$34 = \$37
) + html => qr/./, ), '$3,4+$34,4' => test_zci( '$3,4 + $34,4 = $37,8', heading => 'Calculator', - html => qq(
\$3,4 + \$34,4 = \$37,8
) + html => qr/./, ), '64*343' => test_zci( '64 * 343 = 21,952', heading => 'Calculator', - html => qq(
64 * 343 = 21,952
), + html => qr/./, ), '1E2 + 1' => test_zci( '(1 * 10 ^ 2) + 1 = 101', heading => 'Calculator', - html => qq(
(1 * 102) + 1 = 101
), + html => qr/./, ), '1 + 1E2' => test_zci( '1 + (1 * 10 ^ 2) = 101', heading => 'Calculator', - html => qq(
1 + (1 * 102) = 101
), + html => qr/./, ), '2 * 3 + 1E2' => test_zci( '2 * 3 + (1 * 10 ^ 2) = 106', heading => 'Calculator', - html => - qq(
2 * 3 + (1 * 102) = 106
), + html => qr/./, ), '1E2 + 2 * 3' => test_zci( '(1 * 10 ^ 2) + 2 * 3 = 106', heading => 'Calculator', - html => - qq(
(1 * 102) + 2 * 3 = 106
), + html => qr/./, ), '1E2 / 2' => test_zci( '(1 * 10 ^ 2) / 2 = 50', heading => 'Calculator', - html => qq(
(1 * 102) / 2 = 50
), + html => qr/./, ), '2 / 1E2' => test_zci( '2 / (1 * 10 ^ 2) = 0.02', heading => 'Calculator', - html => qq(
2 / (1 * 102) = 0.02
), + html => qr/./, ), '424334+2253828' => test_zci( '424334 + 2253828 = 2,678,162', heading => 'Calculator', - html => qq(
424334 + 2253828 = 2,678,162
), + html => qr/./, ), '4.243,34+22.538,28' => test_zci( '4.243,34 + 22.538,28 = 26.781,62', heading => 'Calculator', - html => - qq(
4.243,34 + 22.538,28 = 26.781,62
), + html => qr/./, ), 'sin(1,0) + 1,05' => test_zci( 'sin(1,0) + 1,05 = 1,8914709848079', heading => 'Calculator', - html => - qq(
sin(1,0) + 1,05 = 1,8914709848079
), + html => qr/./, ), '21 + 15 x 0 + 5' => test_zci( '21 + 15 x 0 + 5 = 26', heading => 'Calculator', - html => qq(
21 + 15 x 0 + 5 = 26
), + html => qr/./, ), '0.8158 - 0.8157' => test_zci( '0.8158 - 0.8157 = 0.0001', heading => 'Calculator', - html => qq(
0.8158 - 0.8157 = 0.0001
), + html => qr/./, ), '2,90 + 4,6' => test_zci( '2,90 + 4,6 = 7,50', heading => 'Calculator', - html => qq(
2,90 + 4,6 = 7,50
), + html => qr/./, ), '2,90 + sec(4,6)' => test_zci( '2,90 + sec(4,6) = -6,01642861135959', heading => 'Calculator', - html => - qq(
2,90 + sec(4,6) = -6,01642861135959
), + html => qr/./, ), '100 - 96.54' => test_zci( '100 - 96.54 = 3.46', heading => 'Calculator', - html => qq(
100 - 96.54 = 3.46
), + html => qr/./, ), '1. + 1.' => test_zci( '1. + 1. = 2', heading => 'Calculator', - html => qq(
1. + 1. = 2
), + html => qr/./, ), '1 + sin(pi)' => test_zci( '1 + sin(pi) = 1', heading => 'Calculator', - html => qq(
1 + sin(pi) = 1
), + html => qr/./, ), '1 - 1' => test_zci( '1 - 1 = 0', heading => 'Calculator', - html => qq(
1 - 1 = 0
), + html => qr/./, ), 'sin(pi/2)' => test_zci( 'sin(pi / 2) = 1', heading => 'Calculator', - html => qq(
sin(pi / 2) = 1
), + html => qr/./, ), 'sin(pi)' => test_zci( 'sin(pi) = 0', heading => 'Calculator', - html => qq(
sin(pi) = 0
), + html => qr/./, ), 'cos(2pi)' => test_zci( 'cos(2 pi) = 1', heading => 'Calculator', - html => qq(
cos(2 pi) = 1
), + html => qr/./, + ), + '5 squared' => test_zci( + '5 squared = 25', + heading => 'Calculator', + html => qr/./, + ), + '1.0 + 5 squared' => test_zci( + '1.0 + 5 squared = 26.0', + heading => 'Calculator', + html => qr/./, + ), + '3 squared + 4 squared' => test_zci( + '3 squared + 4 squared = 25', + heading => 'Calculator', + html => qr/./, + ), + '2,2 squared' => test_zci( + '2,2 squared = 4,8', + heading => 'Calculator', + html => qr/./, + ), + '0.8^2 + 0.6^2' => test_zci( + '0.8 ^ 2 + 0.6 ^ 2 = 1', + heading => 'Calculator', + html => qr#0.82 \+ 0.62 = 1#, + ), + '2 squared ^ 3' => test_zci( + '2 squared ^ 3 = 256', + heading => 'Calculator', + html => qr#2 squared3 = 256#, + ), + '2^3 squared' => test_zci( + '2 ^ 3 squared = 512', + heading => 'Calculator', + html => qr#23squared = 512#, ), 'sin(1.0) + 1,05' => undef, '4,24,334+22,53,828' => undef,