1
package Language::INTERCAL::Sick;
3
# Compiler/user interface/whatnot for CLC-INTERCAL
5
# This file is part of CLC-INTERCAL
7
# Copyright (c) 2006 Claudio Calvelli, all rights reserved.
9
# CLC-INTERCAL is copyrighted software. However, permission to use, modify,
10
# and distribute it is granted provided that the conditions set out in the
11
# licence agreement are met. See files README and COPYING in the distribution.
20
use vars qw($PERVERSION);
21
$PERVERSION = "CLC-INTERCAL INTERCAL/Sick.pm 1.-94.-4";
23
use Language::INTERCAL::Exporter '1.-94.-4';
24
use Language::INTERCAL::Charset '1.-94.-4', qw(charset_name toascii charset);
25
use Language::INTERCAL::GenericIO '1.-94.-4';
26
use Language::INTERCAL::Backend '1.-94.-4', qw(backend generate_code);
27
use Language::INTERCAL::Interpreter '1.-94.-4';
30
@_ == 1 or croak "Usage: new Language::INTERCAL::Sick";
33
reverse grep {-d $_} map {"$_/Language/INTERCAL/Include"} @INC;
50
default_backend => 'Object',
51
default_charset => [],
57
shared_filepath => {},
64
backend => \&_load_backend,
66
charset => \&_load_charset,
67
default_backend => \&_load_backend,
68
default_charset => \&_load_charset,
69
default_extra => \&_check_extra,
70
default_suffix => \&_check_suffix,
71
include => \&_check_path,
72
optimise => \&_check_bool,
73
preload => \&_check_object,
74
trace => \&_check_filehandle,
76
verbose => \&_check_filehandle,
80
@_ == 2 or @_ == 3 or croak "Usage: SICK->option(NAME [, VALUE])";
81
@_ == 2 ? shift->getoption(@_) : shift->setoption(@_);
85
@_ == 2 or croak "Usage: SICK->getoption(NAME)";
86
my ($sick, $name) = @_;
87
my $value = exists $sick->{object_option}{$name}
88
? $sick->{object_option}{$name}
89
: exists $sick->{shared_option}{$name}
90
? $sick->{shared_option}{$name}
91
: die "Unknown option $name\n";
92
return $value unless ref $value;
93
return $value if UNIVERSAL::isa($value, 'Language::INTERCAL::GenericIO');
94
return @$value if 'ARRAY' eq ref $value;
95
return map { ($_ => [@{$value->{$_}}]) } keys %$value
96
if 'HASH' eq ref $value;
97
return (); # should never get here
101
@_ == 3 or croak "Usage: SICK->setoption(NAME, VALUE)";
102
my ($sick, $name, $value) = @_;
103
my $hash = exists $sick->{object_option}{$name}
104
? $sick->{object_option}
105
: exists $sick->{shared_option}{$name}
106
? $sick->{shared_option}
107
: die "Unknown option $name\n";
108
if (exists $checkoption{$name}) {
109
$value = $checkoption{$name}->($name, $sick, $value);
111
if (! ref $hash->{$name}) {
112
$hash->{$name} = $value;
113
} elsif (UNIVERSAL::isa($value, 'Language::INTERCAL::GenericIO')) {
114
$hash->{$name} = $value;
115
} elsif ('ARRAY' eq ref $hash->{$name}) {
116
push @{$hash->{$name}}, $value;
117
} elsif ('HASH' eq ref $hash->{$name}) {
118
my ($key, $as, @add) = @$value;
119
if (exists $hash->{$name}{$key}) {
120
$hash->{$name}{$key}[0] = $as;
122
$hash->{$name}{$key} = [$as];
124
push @{$hash->{$name}{$key}}, @add;
126
# not supposed to get here
127
die "Cannot set option $name\n";
133
@_ == 2 or croak "Usage: SICK->clearoption(NAME)";
134
my ($sick, $name) = @_;
135
my $hash = exists $sick->{object_option}{$name}
136
? $sick->{object_option}
137
: exists $sick->{shared_option}{$name}
138
? $sick->{shared_option}
139
: die "Unknown option $name\n";
140
if (ref $hash->{$name}) {
141
if (UNIVERSAL::isa($hash->{$name}, 'Language::INTERCAL::GenericIO')) {
143
} elsif ('ARRAY' eq ref $hash->{$name}) {
145
} elsif ('HASH' eq ref $hash->{$name}) {
148
die "Cannot clear option $name\n";
151
die "Cannot clear option $name\n";
157
@_ == 1 or @_ == 2 or croak "Usage: SICK->alloptions [(shared)]";
158
my ($sick, $shared) = @_;
161
push @hash, 'object_option' if ! defined $shared || ! $shared;
162
push @hash, 'shared_option' if ! defined $shared || ! $shared;
163
for my $hash (@hash) {
164
while (my ($name, $value) = each %{$sick->{$hash}}) {
166
# nothing, but we don't want to be caught in next cases
167
} elsif (UNIVERSAL::isa($value, 'Language::INTERCAL::GenericIO')) {
168
# nothing, but we don't want to be caught in next cases
169
} elsif ('ARRAY' eq ref $value) {
170
# a shallow copy will do -- we know values are strings
171
$value = [ @$value ];
172
} elsif ('HASH' eq ref $value) {
173
# two level deep copy: the values are arrays of strings
175
while (my ($key, $val) = each %$value) {
176
$v{$key} = [ @$val ];
179
} elsif (ref $value) {
183
$vals{$name} = $value;
191
or croak "Usage: SICK->source(FILENAME[, LINKIT?])";
192
my ($sick, $file, $linkit) = @_;
194
$file = _check_file($sick, $file);
195
push @{$sick->{sources}}, {
197
'option' => { $sick->alloptions(0) }, # don't copy shared options
198
'filepath' => $sick->{filepath},
201
$sick->{filepath} = {}; # because they might change "include"
207
@_ == 1 or croak "Usage: SICK->load_objects()";
209
return $sick if $sick->{loaded};
210
for (my $i = 0; $i < @{$sick->{sources}}; $i++) {
211
my $object = $sick->{sources}[$i];
212
next if exists $object->{object};
213
my $o = $object->{option};
214
my ($obj, $fn, $base, $is_src) = _load_source($sick, $object, $o);
215
$object->{is_src} = $is_src;
216
$object->{base} = $base;
217
$object->{object} = $obj;
218
$object->{filename} = $fn;
219
if (0) { # XXX linking currently disabled
220
next if $i == 0 || ! $object->{linkit};
221
splice(@{$sick->{sources}}, $i, 1);
223
my $prev = $sick->{sources}[$i];
224
$o->{verbose}->read_text("Linking $fn to $prev->{filename}... ")
226
$prev->{object}->append($object->{object});
227
$o->{verbose}->read_text("\n") if $o->{verbose};
235
@_ == 2 or croak "Usage: SICK->save_objects(AND_KEEP?)";
236
my ($sick, $keep) = @_;
237
$sick->load_objects();
238
for my $object (@{$sick->{sources}}) {
239
my $o = $object->{option};
240
my $backend = $o->{backend};
241
next unless $object->{is_src} || $backend ne 'Object';
242
my $out = $o->{output};
244
$backend = $sick->{shared_option}{default_backend}
246
my $v = $o->{verbose} ? sub {
248
$o->{verbose}->read_text($name eq '' ? 'Running...'
249
: "Saving $name... ");
251
my $orig = $object->{source};
252
$orig =~ s/\.[^.]*$//;
256
generate_code($object->{object}, $backend, $o->{name},
257
$object->{base}, $out, $orig, \%op);
258
$o->{verbose}->read_text("OK\n") if $o->{verbose};
259
undef $object unless $keep;
265
@_ == 2 or croak "Usage: SICK->get_object(NAME)";
266
my ($sick, $name) = @_;
267
for my $o (@{$sick->{sources}}) {
268
next if $o->{source} ne $name;
274
# private methods follow
277
my ($name, $sick, $value) = @_;
278
return $value if $value =~ /^\d+$/;
279
return 1 if $value =~ /^t(?:rue)?$/i;
280
return 1 if $value =~ /^y(?:es)?$/i;
281
return 0 if $value =~ /^f(?:alse)?$/i;
282
return 0 if $value =~ /^n(?:o)?$/i;
283
die "Invalid value for $name\: '$value'\n";
286
sub _check_filehandle {
287
my ($name, $sick, $value) = @_;
288
return $value if ref $value &&
289
UNIVERSAL::isa($value, 'Language::INTERCAL::GenericIO');
290
return 0 if $value =~ /^\d+$/ && $value == 0;
291
return 0 if $value =~ /^n(?:one)?$/i;
292
die "Invalid filehandle value '$value'\n";
296
my ($name, $sick, $value) = @_;
297
return $value if -d $value;
298
die "Invalid path '$value'\n";
302
my ($name, $sick, $value) = @_;
303
$value =~ /^(?:\d+(?:\.\d*)?|\.\d+)$/
304
or die "Value '$value' is not a positive number\n";
306
or die "Value '$value' is too large for a probability\n";
311
my ($name, $sick, $value) = @_;
312
ref $value && ref $value eq 'ARRAY'
313
or die "Invalid value for $name (must be a array ref)\n";
315
or die "Invalid value for $name (requires three elements)\n";
316
my ($extra, $preload, $as) = @$value;
317
ref $preload && ref $preload eq 'ARRAY'
318
or die "Invalid value for $name (preloads must be array ref)\n";
319
[$extra, $preload, $as];
323
my ($name, $sick, $value) = @_;
324
ref $value && ref $value eq 'ARRAY'
325
or die "Invalid value for $name (must be a array ref)\n";
327
or die "Invalid value for $name (requires three elements)\n";
328
my ($suffix, $as, $map) = @$value;
329
ref $map && ref $map eq 'HASH'
330
or die "Invalid value for $name (third element must be hash ref)\n";
331
exists $map->{''} && ref $map->{''} && ref $map->{''} eq 'ARRAY'
332
or die "Invalid value for $name (preloads must be array ref)\n";
335
# suffix map have alternatives expressed as something like
336
# ./2:3:4:5:6:7/i => .2i .3i ... .7i
337
# ./l:n:g:t://i => .li .ni .gi .ti .lni .nli ...
338
# note that we have no nesting of alternatives; use different rules
339
while ($suffix =~ s#^(.*?)/##) {
340
$regex .= quotemeta($1);
341
$suffix =~ s#^(.*?)/##
342
or die "Invalid value for $name\: unclosed / in suffix\n";
343
my @extra = split(/:/, $1);
344
for my $extra (@extra) {
345
exists $map->{$extra} or next;
346
ref $map->{$extra} && ref $map->{$extra} eq 'ARRAY'
347
or die "Invalid value for $name " .
348
"(preloads for $extra must be array ref)\n";
350
my $extra = join('|', map { quotemeta } @extra);
351
my $star = $suffix =~ s#^:## ? '*' : '';
352
$regex .= '((?:' . $extra . ')' . $star . ')';
353
push @resplit, qr/^($extra)/;
355
$regex .= $suffix . '$';
356
[qr/$regex/, $as, \@resplit, $map];
360
my ($sick, $value, $ftype, $cache, $path) = @_;
361
return $cache->{$value} if exists $cache->{$value};
362
# try opening file from current directory
364
$cache->{$value} = $value;
367
if (! File::Spec->file_name_is_absolute($value)) {
368
my ($file, $dir) = fileparse($value);
369
$path = $sick->{object_option}{include} if ! defined $path;
370
for my $search (reverse @$path) {
371
my $n = File::Spec->catfile($search, $dir, $file);
372
$n = File::Spec->canonpath($n);
374
$cache->{$value} = $n;
379
die "Cannot find $ftype \"$value\"\n";
383
my ($sick, $value) = @_;
384
_find_file($sick, $value, 'file',
386
$sick->{object_option}{include});
391
my ($sick, $value, $cache, $path) = @_;
392
if ($value !~ /\.io$/) {
393
# try adding suffix first
395
_find_file($sick, $value . '.io', 'object', $cache, $path);
399
_find_file($sick, $value, 'object', $cache, $path);
403
my ($name, $sick, $value) = @_;
404
_find_object($sick, $value,
406
$sick->{object_option}{include});
411
my ($sick, $source, $cache, $path) = @_;
412
my $fn = _find_file($sick, $source, 'file', $cache, $path);
413
my $fh = Language::INTERCAL::GenericIO->new('FILE', 'w', $fn);
418
my ($name, $sick, $value) = @_;
419
defined backend($value)
420
or die "Invalid backend: $value";
425
my ($name, $sick, $value) = @_;
426
defined charset_name($value)
427
or die "Invalid charset: $value\n";
432
my ($sick, $source, $o) = @_;
433
my ($fn, $fh) = _open_file($sick, $source->{source},
434
$source->{filepath}, $o->{include});
435
$o->{verbose}->read_text("$fn... ") if $o->{verbose};
439
$suffix = $o->{suffix};
440
$suffix = '.' . $suffix if $suffix !~ /^\./;
441
$base =~ s/(\.[^.]*)$//; # remove and ignore suffix
442
} elsif ($base =~ s/(\.[^.]*)$//) {
445
# XXX check for a fully compiled object and load it if found
446
# first see if it is a real object (you never know)
448
Language::INTERCAL::Interpreter->write($fh);
450
if (defined $int && ref $int) {
451
$o->{verbose}->read_text("[COMPILER OBJECT]\n") if $o->{verbose};
453
$int->setreg('TRFH', $o->{trace});
454
$int->setreg('TM', 1);
456
return ($int, $fn, $base, 0);
458
# failed for whatever reason, we'll try loading as a source
460
my @preload = @{$o->{preload}};
461
@preload = _guess_preloads($sick, $suffix, $o)
463
$int = Language::INTERCAL::Interpreter->new();
465
$int->setreg('TRFH', $o->{trace});
466
$int->setreg('TM', 1);
468
my $obj = $int->object;
470
$obj->setbug(0, $o->{bug});
472
$obj->setbug(1, $o->{ubug});
475
# preload all the required things
476
for my $p (@preload, 'postpre') {
478
_preload($sick, $p, $source->{filepath}, $o, $int);
481
# do we need to guess character set?
482
my $chr = $o->{charset};
484
$chr = _guess_charset($sick, $source->{source}, $fh);
486
$fh->write_charset($chr);
492
my $text = $fh->write_text('');
493
$o->{verbose}->read_text("\n source: " . length($text) . " bytes")
496
$int->compile($text);
497
$o->{verbose}->read_text(" [object: " . _int_size($obj) . " bytes]")
499
$o->{verbose}->read_text("\n") if $o->{verbose};
500
return ($int, $fn, $base, 1);
504
my ($sick, $file, $cache, $o, $int) = @_;
505
my $fn = _find_object($sick, $file, $cache, $o->{include});
506
$o->{verbose}->read_text("\n [$file: $fn") if $o->{verbose};
508
if (exists $sick->{int_cache}{$fn}) {
509
($ci, $size) = @{$sick->{int_cache}{$fn}};
510
if ($o->{verbose} && ! $size) {
511
$sick->{int_cache}{$fn}[1] = $size = _int_size($ci);
514
my $fh = Language::INTERCAL::GenericIO->new('FILE', 'w', $fn);
515
$ci = Language::INTERCAL::Interpreter->write($fh);
516
$size = $o->{verbose} ? _int_size($ci) : 0;
517
$sick->{int_cache}{$fn} = [$ci, $size];
519
$o->{verbose}->read_text(": $size bytes]") if $o->{verbose};
520
$int->start()->run($ci)->stop();
521
exit 1 if defined $int->splat;
525
my ($sick, $extra) = @_;
526
for my $xd (@{$sick->{shared_option}{default_extra}}) {
527
my ($x, $preload, $as) = @$xd;
528
next if $x ne $extra;
529
return ($preload, $as);
534
sub _guess_preloads {
535
my ($sick, $suffix, $o) = @_;
536
# must guess preloads from suffix
537
for my $sd (@{$sick->{shared_option}{default_suffix}}) {
538
my ($regex, $as, $resplit, $map) = @$sd;
539
my @extra = $suffix =~ $regex;
543
for my $r (@$resplit) {
544
my $e = shift @extra;
545
next unless defined $e;
546
while ($e =~ s/$r//) {
549
die "Internal error in _guess_preloads\n" if $e ne '';
557
for my $p (@{$map->{''}}) {
560
next unless $o->{optimise};
566
my %as = ( $as => 1 );
567
for my $extra (@extra) {
569
if (exists $map->{$extra}) {
570
($_p, $a) = @{$map->{$extra}};
572
($_p, $a) = _guess_extra($sick, $extra);
573
die "Inconsistent sickrc: $extra?\n" unless defined $_p;
578
next unless $o->{optimise};
580
next if exists $preloads{$q};
584
next if $a eq '' || exists $as{$a};
588
$o->{verbose}->read_text(" [" . join(' + ', @as) . "]")
592
die "Cannot guess file type\n";
596
my ($sick, $source, $fh) = @_;
598
for my $name (@{$sick->{shared_option}{default_charset}}) {
600
my $cnv = toascii($name);
602
while ((my $line = $fh->write_binary(4096)) ne '') {
603
my $cl = &$cnv($line);
604
$count++ while $line =~ /DO|PLEASE/ig;
606
$counts{$name} = $count;
611
sort {$counts{$b} <=> $counts{$a}} grep {$counts{$_}} keys %counts;
612
if (@counts == 0 && $fh->write_binary(1) eq '') {
617
if (! @counts || $counts{$counts[0]} < 1) {
618
my $cr = $sick->{object_option}{verbose} ? "\n" : "";
619
die "${cr}File \"$source\": cannot guess character set\n";
627
my $fh = new Language::INTERCAL::GenericIO 'COUNT', 'r', \$size;