5 # noremap <C-w> V:!~/src/jaxam --wrong --tee --errors-wait-key<cr>
10 require Term::ReadKey;
11 require Tree::Binary::Search;
13 require Time::Piece::ISO;
17 binmode STDERR,":utf8"; # only STDIN and STDOUT covered by: use encoding "utf8";
21 my $opt_edict="$0.edict";
24 my $opt_errors_wait_key;
28 $Getopt::Long::ignorecase=0;
29 $Getopt::Long::bundling=1;
31 "d|debug+" =>\$opt_debug,
32 "e|edict=s" =>\$opt_edict,
33 "l|log=s" =>\$opt_log,
34 "w|wrong" =>\$opt_wrong, # Filter the input lines.
35 "errors-wait-key"=>\$opt_errors_wait_key, # Only for -w|--wrong.
36 "tee!" =>\$opt_tee, # Output all the read lines. Only for -w|--wrong.
39 $opt_log||=$opt_edict.".log";
42 "kana" =>{"to"=>"english"}, # "prob"=>1,
43 "english"=>{"to"=>"kana"}, # "prob"=>1,
57 my($r,$exam_from,%args)=@_;
59 my $time=$args{"time"}||time();
61 for my $result (@{$r->{"result"}}) {
62 next if $result->{"exam_from"} ne $exam_from;
63 my $age=$time-$result->{"time"};
65 # $age: 0.. big importance
66 # $age: big..small importance
71 # 1/log(2+$age): big.. big importance
72 # 1/log(2+$age): 0..small importance
73 $sum+=1/log(2+$age)*$ok{$result->{"ok"}};
76 if ($T->exists($sum)) {
77 $arr=$T->select($sum)->{"arr"};
80 splice @$arr,int(rand(@$arr+1)),0,{"r"=>$r,"exam_from"=>$exam_from};
81 $T->insert($sum=>{"key"=>$sum,"arr"=>$arr});
82 print STDERR "; schedule=$sum" if $opt_debug;
87 my($opt_debug_force)=@_;
89 $T=Tree::Binary::Search->new();
90 $T->useNumericComparison();
93 local $opt_debug=0 if $opt_debug<2;
94 local $opt_debug=$opt_debug_force if defined $opt_debug_force;
95 print STDERR $r->{"kana"}."\t".$r->{"english"} if $opt_debug;
96 for my $exam_from (keys(%exam)) {
97 schedule $r,$exam_from,"time"=>$time;
99 print STDERR "\n" if $opt_debug;
105 return if $T->isEmpty();
106 my $min=$T->min()->{"key"};
107 my $arr=$T->select($min)->{"arr"};
110 $T->insert($min=>{"key"=>$min,"arr"=>$arr}) if @$arr;
112 print STDERR "min=$min";
113 print STDERR " of ".(1+@$arr) if @$arr;
124 map(($r->{$_}||()),qw(
128 (!$args{"at"} ? () : '@'.$r->{"line"});
133 my($r,$exam_from,$ok,%args)=@_;
135 if ($args{"write"}) {
137 open LOG_APPEND,">>:utf8",$opt_log or die "append \"$opt_log\": $!";
138 print LOG_APPEND Time::Piece::ISO::localtime().POSIX::strftime("%z",localtime())
139 ." exam_from=$exam_from ok=$ok: ".identify($r)."\n";
140 close LOG_APPEND or die "close \"$opt_log\": $!";
143 push @{$r->{"result"}},{
145 "exam_from"=>$exam_from,
150 my $errors_wait_key_count;
152 if ($errors_wait_key_count && $opt_errors_wait_key) {
153 print STDERR "Errors occured. Press any key to continue...";
158 sub edict_line_parse(;$)
162 $s=$_ if !defined $s;
163 $line=$. if !defined $s;
168 #〒 [ゆうびん] /(n) mail/postal service/
170 m{^([^[/\s]+)(?:\s+[[]([^[/\s]+)[]])?\s+/(.*)$}
171 or do { warn "Unparsable: $s\n"; $errors_wait_key_count++; return; };
174 ($r->{"kanji"},$r->{"kana"},$rest)=($1,$2,$3);
176 $r->{"kana"}=$r->{"kanji"};
177 delete $r->{"kanji"};
180 $rest=~s{^\s*([^/]+?)\s*/}{}
181 or do { warn "Unparsable english: $s\n"; $errors_wait_key_count++; return; };
183 push @{$r->{"english"}},$english;
196 s/\bto\b//g if $type eq "english";
207 open EDICT,"<:utf8",$pathname or die "open \"$pathname\": $!";
209 #"kanji"=>{}, # exists...
210 #"kana"=>{}, # exists...
214 my $r=edict_line_parse() or next;
215 next if /\Q(laceno)\E/;
217 push @{$kana_to_r{$r->{"kana"}}},$r;
219 while (my($field,$hashref)=each(%check)) {
221 $val=identify $r if $field eq "identify";
223 next if $val eq "悪い にくい"; # fixup: <20050522015353.GA31030@kashome.dyn.jankratochvil.net>
224 my $origvalref=\$hashref->{$val};
225 warn "field{$field} check duplicity: ".identify($r,"at"=>1)." vs. ".identify($$origvalref,"at"=>1)
230 close EDICT or die "close \"$pathname\": $!";
231 %identify=map((identify($_)=>$_),@R);
239 if (!open LOG_READ,"<:utf8",$pathname) {
240 warn "open \"$pathname\": $!";
245 # 2002-04-25T21:17:52+0900 ok=1: identify($r)
246 my($year, $month, $day, $hour, $minute,$second,$zonepm,$zonehour,$zoneminute,$exam_from,$ok,$identify)=
247 /^(\d{4})-(\d{1,2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-])(\d{2})(\d{2}) exam_from=(\w+) ok=(\d+): (.+)$/
248 or do { warn "Unrecognized line: $_\n"; next; };
251 my $localtime=POSIX::mktime($second,$minute,$hour,$day,$month,$year);
252 $localtime or do { warn "Unparsable time at line: $_\n"; next; };
253 my $gmtime=$localtime-($zonepm."1")*($zonehour*60+$zoneminute);
254 my $r=$identify{$identify}
255 or do { warn "Word not found from line: $_\n"; next; };
256 result $r,$exam_from,$ok,"write"=>0;
258 close LOG_READ or die "close \"$pathname\": $!";
266 my $w=edict_line_parse() or next;
267 my $r=$identify{identify $w};
268 do { warn "'wrong word' not found for the line: $_\n"; $errors_wait_key_count++; next; } if !$r;
269 result $r,"kana",0,"write"=>1;
277 return $s if !ref $s;
278 return join("",map("$_/",@$s));
283 my @exam=keys(%exam);
285 reschedule() if !$opt_fast;
286 my $besthashref=best();
287 my $r=$besthashref->{"r"};
288 my $exam_from=$besthashref->{"exam_from"};
289 my $exam_to=$exam{$exam_from}{"to"};
290 print word_out($r->{$exam_from}).": ";
293 my $want=$r->{$exam_to};
294 my $got_chk=to_chk($got,$exam_to);
295 my @want_chk=map(to_chk($_,$exam_to),map((!ref($_)?$_:@$_),$want));
298 for my $want_chk (@want_chk) {
299 next if !$want_chk; # discard patterns like: /(P)/
300 $ok=1 if $got_chk eq $want_chk;
301 $substr=1 if $want_chk=~/\Q$got_chk\E/;
304 if (!$ok && $exam_from eq "english" && $exam_to eq "kana"
305 && (my $arrref=$kana_to_r{$got})) {
306 my %chk_english_from=map((to_chk($_,"english")=>1),@{$r->{"english"}});
308 for my $kana_alias (@$arrref) {
309 for my $kana_alias_english (@{$kana_alias->{"english"}}) {
310 my $chk_english=to_chk $kana_alias_english,"english" or next;
311 next if !$chk_english_from{$chk_english};
312 print "intended ----> ".$r->{"orig"}."\n" if !$intended++;
313 print "OK if alias -> ".$kana_alias->{"orig"}."\n";
319 print "-------------> ".word_out($want)."\n" if !$aliased;
321 if ($exam_to eq "english" && $got=~/\S/ && $substr) {
324 print STDERR "Is your answer correct? [y/n] ";
327 Term::ReadKey::ReadMode(0);
329 local $SIG{"__DIE__"}=\&restore;
330 Term::ReadKey::ReadMode(4);
331 $key=Term::ReadKey::ReadKey(0);
332 print STDERR "\n"; # no echo
334 die if $key eq "\x03"; # ctrl-c
335 $ok=1 if $key eq "y";
336 $ok=0 if $key eq "n";
337 } while (!defined $ok);
342 result $r,$exam_from,$ok,"write"=>1;
343 print STDERR "result: ".($ok ? "ok" : "WRONG");
344 schedule $r,$exam_from;
347 print STDERR join(" * ",map("WRONG",1..8))."\n" if !$ok;
351 # FIXME: balanced $exam_from:
352 # my $exam_prob_sum=0;
353 # $exam_prob_sum+=$_->{"prob"} for values(%exam);
355 # my $rand=int rand $exam_prob_sum;
358 # while (my($from,$hashref)=each(%exam)) {
359 # $sum+=$hashref->{"prob"};
360 # next if $rand>=$sum;
364 # die "INTERNAL" if !$exam_from;
367 edict_read $opt_edict;
369 do { wrong_read(); exit; } if $opt_wrong;
372 reschedule $opt_debug>=2; # init