11package DDG::Goodie::Calculator ;
22# ABSTRACT: do simple arthimetical calculations
33
4+ use feature ' state' ;
5+
46use DDG::Goodie;
57
68use List::Util qw( all first max ) ;
@@ -21,27 +23,6 @@ attribution
2123 github => [' https://github.com/duckduckgo' , ' duckduckgo' ],
2224 twitter => [' http://twitter.com/duckduckgo' , ' duckduckgo' ];
2325
24- triggers query_nowhitespace => qr <
25- ^
26- ( what is | calculate | solve | math )? !?
27-
28- [\( \) x X * % + / \^ \$ -]*
29-
30- (?: [0-9 \. ,]* )
31- (?: gross | dozen | pi | e | c |)
32- [\( \) x X * % + / \^ 0-9 \. , \$ -]*
33-
34- (?(1) (?: -? [0-9 \. ,]+ |) |)
35- (?: [\( \) x X * % + / \^ \$ -] | times | divided by | plus | minus | cos | sin | tan | cotan | log | ln | log[_]?\d {1,3} | exp | tanh | sec | csc)+
36-
37- (?: [0-9 \. ,]* )
38- (?: gross | dozen | pi | e | c |)
39-
40- [\( \) x X * % + / \^ 0-9 \. , \$ -]* =?
41-
42- $
43- > xi ;
44-
4526# This is probably YAGNI territory, but since I have to reference it in two places
4627# and there are a multitude of other notation systems (although some break the
4728# 'thousands' assumption) I am going to pretend that I do need it.
@@ -74,7 +55,7 @@ foreach my $style (@known_styles) {
7455# Luckily it will someday be able to be tokenized so this won't apply.
7556my $all_seps = join (' ' , map { $_ -> {decimal } . $_ -> {thousands } } @known_styles );
7657
77- my $numbery = qr /^ [\d $all_seps ]+$ / ;
58+ my $numbery = qr / [\d $all_seps ]+/ ;
7859my $funcy = qr / [[a-z]+\( |log[_]?\d {1,3}\( |\^ / ; # Stuff that looks like functions.
7960
8061my %named_operations = (
@@ -85,8 +66,11 @@ my %named_operations = (
8566 ' plus' => ' +' ,
8667 ' divided\sby' => ' /' ,
8768 ' ln' => ' log' , # perl log() is natural log.
69+ ' squared' => ' **2' ,
8870);
8971
72+ my $ored_operations = join (' |' , keys %named_operations );
73+
9074my %named_constants = (
9175 dozen => 12,
9276 e => 2.71828182845904523536028747135266249, # This should be computed.
@@ -96,12 +80,19 @@ my %named_constants = (
9680
9781my $ored_constants = join (' |' , keys %named_constants ); # For later substitutions
9882
83+ my $extra_trigger_words = qr / ^(?:whatis|calculate|solve|math)/ ;
84+ triggers query_nowhitespace => qr <
85+ $extra_trigger_words ?
86+ (\s |$funcy |$ored_constants |$ored_operations |$numbery )*
87+ $
88+ > xi ;
89+
9990handle query_nowhitespace => sub {
10091 my $results_html ;
10192 my $results_no_html ;
10293 my $query = $_ ;
10394
104- $query =~ s /^(?:whatis|calculate|solve|math) // ;
95+ $query =~ s /$extra_trigger_words // ;
10596
10697 if ($query !~ / [xX]\s *[\*\%\+\-\/\^ ]/ && $query !~ / ^-?[\d ]{2,3}\.\d +,\s ?-?[\d ]{2,3}\.\d +$ / ) {
10798 my $tmp_result = ' ' ;
@@ -125,7 +116,7 @@ handle query_nowhitespace => sub {
125116 $tmp_expr =~ s #\b $name\b # $constant # ig ;
126117 }
127118
128- my @numbers = grep { $_ =~ $numbery } (split /\s +/, $tmp_expr );
119+ my @numbers = grep { $_ =~ / ^ $numbery $ / } (split /\s +/, $tmp_expr );
129120 my $style = display_style(@numbers );
130121 return unless $style ;
131122
@@ -137,6 +128,7 @@ handle query_nowhitespace => sub {
137128 # e.g. sin(100000)/100000 completely makes this go haywire.
138129 alarm(1);
139130 $tmp_result = eval ($tmp_expr );
131+ alarm(0); # Assume the string processing will be "fast enough"
140132 };
141133
142134 # Guard against non-result results
@@ -165,8 +157,7 @@ handle query_nowhitespace => sub {
165157 $results_no_html = $results_html = $tmp_q ;
166158
167159 # Superscript (before spacing).
168- $results_html =~ s /\^ ([^\) ]+)/ <sup>$1 <\/ sup>/ g ;
169- $results_html =~ s /\^ (\d +|\b (?:$ored_constants)\b )/ <sup>$1 <\/ sup>/ g ;
160+ $results_html =~ s /\^ ($numbery|\b $ored_constants\b )/ <sup>$1 <\/ sup>/ g ;
170161
171162 ($results_no_html , $results_html ) = map { spacing($_ ) } ($results_no_html , $results_html );
172163 return if $results_no_html =~ / ^\s / ;
@@ -176,26 +167,37 @@ handle query_nowhitespace => sub {
176167
177168 # Now add = back.
178169 $results_no_html .= ' = ' ;
179- $results_html .= ' = ' ;
180170
181- $results_html =
182- qq( <div>$results_html <a href="javascript:;" onClick="document.x.q.value='$tmp_result ';document.x.q.focus();">$tmp_result </a></div>) ;
183171 return $results_no_html . $tmp_result ,
184- html => $results_html ,
172+ html => wrap_html( $results_html , $tmp_result ) ,
185173 heading => " Calculator" ;
186174 }
187175
188176 return ;
189177};
190178
179+ # Add some HTML and styling to our output
180+ # so that we can make it prettier (unabashedly stolen from
181+ # the ReverseComplement goodie.)
182+ sub append_css {
183+ my $html = shift ;
184+ state $css = share(" style.css" )-> slurp;
185+ return " <style type='text/css'>$css </style>$html " ;
186+ }
187+
188+ sub wrap_html {
189+ my ($entered , $result ) = @_ ;
190+ return append_css(" <div class='zci--calculator'>$entered = $result </div>" );
191+ }
192+
191193# separates symbols with a space
192194# spacing '1+1' -> '1 + 1'
193195sub spacing {
194196 my ($text , $space_for_parse ) = @_ ;
195197
196198 $text =~ s / (\s *(?<!<)(?:[\+\-\^ xX\*\/\% ]|times|plus|minus|dividedby)+\s *)/ $1 / ig ;
197199 $text =~ s /\s *dividedby\s */ divided by / ig ;
198- $text =~ s / (\d +?)((?:dozen|pi|gross))/ $1 $2 / ig ;
200+ $text =~ s / (\d +?)((?:dozen|pi|gross|squared ))/ $1 $2 / ig ;
199201 $text =~ s / (\d +?)e/ $1 e/ g ; # E == *10^n
200202 $text =~ s / ([\(\)\$ ])/ $1 / g if ($space_for_parse );
201203
0 commit comments