9 require Tree::Binary::Search;
11 require Time::Piece::ISO;
15 binmode STDERR,":utf8"; # only STDIN and STDOUT covered by: use encoding "utf8";
19 my $opt_edict="$0.edict";
22 my $opt_errors_wait_key;
26 $Getopt::Long::ignorecase=0;
27 $Getopt::Long::bundling=1;
29 "d|debug+" =>\$opt_debug,
30 "e|edict=s" =>\$opt_edict,
31 "l|log=s" =>\$opt_log,
32 "w|wrong" =>\$opt_wrong, # Filter the input lines.
33 "errors-wait-key"=>\$opt_errors_wait_key, # Only for -w|--wrong.
34 "tee!" =>\$opt_tee, # Output all the read lines. Only for -w|--wrong.
37 $opt_log||=$opt_edict.".log";
40 "kana" =>{"to"=>"english"}, # "prob"=>1,
41 "english"=>{"to"=>"kana"}, # "prob"=>1,
55 my($r,$exam_from,%args)=@_;
57 return if $r->{"orig"}=~/\Q(laceno)\E/;
58 my $time=$args{"time"}||time();
60 for my $result (@{$r->{"result"}}) {
61 next if $result->{"exam_from"} ne $exam_from;
62 my $age=$time-$result->{"time"};
64 # $age: 0.. big importance
65 # $age: big..small importance
70 # 1/log(2+$age): big.. big importance
71 # 1/log(2+$age): 0..small importance
72 $sum+=1/log(2+$age)*$ok{$result->{"ok"}};
76 my $abspri=$r->{"pri"};
77 $abspri=1/(-$abspri) if $abspri<0;
78 $abspri=2 if $abspri<2;
79 $scale=log($abspri)*($r->{"pri"}<0 ? -1 : +1);
82 if ($T->exists($sum)) {
83 $arr=$T->select($sum)->{"arr"};
86 splice @$arr,int(rand(@$arr+1)),0,{"r"=>$r,"exam_from"=>$exam_from};
87 $T->insert($sum=>{"key"=>$sum,"arr"=>$arr});
88 print STDERR "; schedule=$sum" if $opt_debug;
93 my($opt_debug_force)=@_;
95 $T=Tree::Binary::Search->new();
96 $T->useNumericComparison();
99 local $opt_debug=0 if $opt_debug<2;
100 local $opt_debug=$opt_debug_force if defined $opt_debug_force;
101 print STDERR $r->{"kana"}."\t".$r->{"english"} if $opt_debug;
102 for my $exam_from (keys(%exam)) {
103 schedule $r,$exam_from,"time"=>$time;
105 print STDERR "\n" if $opt_debug;
111 return if $T->isEmpty();
112 my $min=$T->min()->{"key"};
113 my $arr=$T->select($min)->{"arr"};
116 $T->insert($min=>{"key"=>$min,"arr"=>$arr}) if @$arr;
118 print STDERR "min=$min";
119 print STDERR " of ".(1+@$arr) if @$arr;
130 map(($r->{$_}||()),qw(
134 (!$args{"at"} ? () : '@'.$r->{"line"});
139 my($r,$exam_from,$ok,%args)=@_;
141 if ($args{"write"}) {
143 open LOG_APPEND,">>:utf8",$opt_log or die "append \"$opt_log\": $!";
144 print LOG_APPEND Time::Piece::ISO::localtime().POSIX::strftime("%z",localtime())
145 ." exam_from=$exam_from ok=$ok: ".identify($r)."\n";
146 close LOG_APPEND or die "close \"$opt_log\": $!";
149 push @{$r->{"result"}},{
151 "exam_from"=>$exam_from,
156 my $errors_wait_key_count;
158 if ($errors_wait_key_count && $opt_errors_wait_key) {
159 print STDERR "Errors occured. Press any key to continue...";
164 sub edict_line_parse(;$)
168 $s=$_ if !defined $s;
169 $line=$. if !defined $line;
174 #〒 [ゆうびん] /(n) mail/postal service/
176 m{^([^[/\s]+)(?:\s+[[]([^[/\s]+)[]])?\s+/(.*)$}
177 or do { warn "Unparsable: $s\n"; $errors_wait_key_count++; return; };
180 ($r->{"kanji"},$r->{"kana"},$rest)=($1,$2,$3);
182 $r->{"kana"}=$r->{"kanji"};
183 delete $r->{"kanji"};
185 $rest=~s{^(|.*/)[(]pri([+-]\d+)[)]/}{$1};
186 $r->{"pri"}=$2 if $2;
188 $rest=~s{^\s*([^/]+?)\s*/}{}
189 or do { warn "Unparsable english: $s\n"; $errors_wait_key_count++; return; };
191 push @{$r->{"english"}},$english;
204 s/\bto\b//g if $type eq "english";
215 open EDICT,"<:utf8",$pathname or die "open \"$pathname\": $!";
217 #"kanji"=>{}, # exists...
218 #"kana"=>{}, # exists...
222 my $r=edict_line_parse() or next;
224 push @{$kana_to_r{$r->{"kana"}}},$r;
226 while (my($field,$hashref)=each(%check)) {
228 $val=identify $r if $field eq "identify";
230 next if $val eq "悪い にくい"; # fixup: <20050522015353.GA31030@kashome.dyn.jankratochvil.net>
231 my $origvalref=\$hashref->{$val};
232 warn "field{$field} check duplicity: ".identify($r,"at"=>1)." vs. ".identify($$origvalref,"at"=>1)
237 close EDICT or die "close \"$pathname\": $!";
238 %identify=map((identify($_)=>$_),@R);
246 if (!open LOG_READ,"<:utf8",$pathname) {
247 warn "open \"$pathname\": $!";
252 # 2002-04-25T21:17:52+0900 ok=1: identify($r)
253 my($year, $month, $day, $hour, $minute,$second,$zonepm,$zonehour,$zoneminute,$exam_from,$ok,$identify)=
254 /^(\d{4})-(\d{1,2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-])(\d{2})(\d{2}) exam_from=(\w+) ok=(\d+): (.+)$/
255 or do { warn "Unrecognized line: $_\n"; next; };
258 my $localtime=POSIX::mktime($second,$minute,$hour,$day,$month,$year);
259 $localtime or do { warn "Unparsable time at line: $_\n"; next; };
260 my $gmtime=$localtime-($zonepm."1")*($zonehour*60+$zoneminute);
261 my $r=$identify{$identify}
262 or do { warn "Word not found from line: $_\n"; next; };
263 result $r,$exam_from,$ok,"write"=>0;
265 close LOG_READ or die "close \"$pathname\": $!";
273 my $w=edict_line_parse() or next;
274 my $r=$identify{identify $w};
275 do { warn "'wrong word' not found for the line: $_\n"; $errors_wait_key_count++; next; } if !$r;
276 result $r,"kana",0,"write"=>1;
284 return $s if !ref $s;
285 return join("",map("$_/",@$s));
290 my @exam=keys(%exam);
292 reschedule() if !$opt_fast;
293 my $besthashref=best();
294 my $r=$besthashref->{"r"};
295 my $exam_from=$besthashref->{"exam_from"};
296 my $exam_to=$exam{$exam_from}{"to"};
297 print word_out($r->{$exam_from}).": ";
300 my $want=$r->{$exam_to};
301 my $got_chk=to_chk($got,$exam_to);
302 my @want_chk=map(to_chk($_,$exam_to),map((!ref($_)?$_:@$_),$want));
305 for my $want_chk (@want_chk) {
306 next if !$want_chk; # discard patterns like: /(P)/
307 $ok=1 if $got_chk eq $want_chk;
308 $substr=1 if $want_chk=~/\Q$got_chk\E/;
311 if (!$ok && $exam_from eq "english" && $exam_to eq "kana"
312 && (my $arrref=$kana_to_r{$got})) {
313 my %chk_english_from=map((to_chk($_,"english")=>1),@{$r->{"english"}});
315 for my $kana_alias (@$arrref) {
316 for my $kana_alias_english (@{$kana_alias->{"english"}}) {
317 my $chk_english=to_chk $kana_alias_english,"english" or next;
318 next if !$chk_english_from{$chk_english};
319 print "intended ----> ".$r->{"orig"}."\n" if !$intended++;
320 print "OK if alias -> ".$kana_alias->{"orig"}."\n";
326 print "-------------> ".word_out($want)."\n" if !$aliased;
328 if ($exam_to eq "english" && $got=~/\S/ && $substr) {
331 print STDERR "Is your answer correct? [y/n] ";
334 Term::ReadKey::ReadMode(0);
336 local $SIG{"__DIE__"}=\&restore;
337 Term::ReadKey::ReadMode(4);
338 $key=Term::ReadKey::ReadKey(0);
339 print STDERR "\n"; # no echo
341 die if $key eq "\x03"; # ctrl-c
342 $ok=1 if $key eq "y";
343 $ok=0 if $key eq "n";
344 } while (!defined $ok);
349 result $r,$exam_from,$ok,"write"=>1;
350 print STDERR "result: ".($ok ? "ok" : "WRONG");
351 schedule $r,$exam_from;
354 print STDERR join(" * ",map("WRONG",1..8))."\n" if !$ok;
358 # FIXME: balanced $exam_from:
359 # my $exam_prob_sum=0;
360 # $exam_prob_sum+=$_->{"prob"} for values(%exam);
362 # my $rand=int rand $exam_prob_sum;
365 # while (my($from,$hashref)=each(%exam)) {
366 # $sum+=$hashref->{"prob"};
367 # next if $rand>=$sum;
371 # die "INTERNAL" if !$exam_from;
374 edict_read $opt_edict;
376 do { wrong_read(); exit; } if $opt_wrong;
379 reschedule $opt_debug>=2; # init