-#!/usr/bin/perl
+#!/usr/bin/perl -w
use POSIX;
# $loc{$id}{Y}
# $loc{$id}{A} may be undef
+open DEBUG, ($debug ? ">&2" : ">/dev/null") or die $!;
+
sub canf ($$) {
my ($converter,$defaulter)=@_;
- my ($spec);
- &$defaulter unless @al;
+ my ($spec,$v);
+ return &$defaulter unless @al;
$spec= shift @al;
- return &$converter($spec);
+ $v= &$converter($spec);
+ dv('canf ','$spec',$spec, '$v',$v);
+ return $v;
}
-
-sub can ($) { my ($conv)=@_; canf($c, sub { die "too few args"; }); }
-sub cano ($$) { my ($conv,$def)=@_; canf($c, sub { return $def }); }
+sub can ($) { my ($c)=@_; canf($c, sub { die "too few args"; }); }
+sub cano ($$) { my ($c,$def)=@_; canf($c, sub { return $def }); }
$pi= atan2(0,-1);
-%units_len= qw(- mm mm 0 cm 10 m 1000);
-%units_ang= qw(- d r 1); $units_ang{'d'}= 2*$pi/360;
+%units_len= qw(- mm mm 1 cm 10 m 1000);
+%units_ang= qw(- d r 1); $units_ang{'d'}= 2*$pi / 360;
sub cva_len ($) { my ($sp)=@_; cva_units($sp,\%units_len); }
sub cva_ang ($) { my ($sp)=@_; cva_units($sp,\%units_ang); }
sub cva_units ($$) {
- my ($sp,$ua,$n,$u)=@_;
- $sp =~ s/^([-0-9eE.]+)([A-Za-z]*)$/;
- ($n,$u)= ($1,$1);
- $u=$u{'-'} unless length $u;
+ my ($sp,$ua)=@_;
+ my ($n,$u,$r);
+ $sp =~ m/^([-0-9eE.]*[0-9.])([A-Za-z]*)$/
+ or die "lexically invalid quantity";
+ ($n,$u)= ($1,$2);
+ $u=$ua->{'-'} unless length $u;
defined $ua->{$u} or die "unknown unit $u";
- return $n * $ua->{$u};
+ $r= $n * $ua->{$u};
+ print DEBUG "cva_units($sp,)=$r ($n $u $ua->{$u})\n";
+ return $r;
+}
+sub cva_idstr ($) {
+ my ($sp)=@_;
+ die "invalid id" unless $sp =~ m/^[-0-9a-z]+$/;
+ return $&;
}
-sub cva_idstr ($) { my ($sp)=@_; die unless m/^[-0-9a-z]+$; return $&; }
sub cva_idex ($) {
my ($sp,$id)=@_;
+ my ($r);
$id=cva_idstr($sp);
die "unknown $id" unless defined $loc{$id};
- return $loc{$id};
+ $r= $loc{$id};
+ printf DEBUG "%s %s %s\n", $id, join("|", keys %$r), $r->{A};
+ return $r;
}
sub cva_idnew ($) {
my ($sp,$id)=@_;
- $id=cva_idstr($sp);
- die "duplicate $id" if defined $loc{$id};
- keys %{ $loc{$id} };
+ $id=cva_idstr($sp);
+ die "duplicate $id" if exists $loc{$id};
+ exists $loc{$id}{X};
return $loc{$id};
-}
+}
+sub cva_cmd ($) {
+ my ($sp)=@_;
+ die "command lexically invalid" if $sp =~ m/[^-0-9a-z]/i;
+ $sp =~ y/-/_/;
+ return $sp;
+}
+sub cva__enum ($$) {
+ my ($sp,$el)=@_;
+ return $sp if grep { $_ eq $sp } @$el;
+ die "invalid option (permitted: @$el)";
+}
+sub cvam_enum { my (@e) = @_; return sub { cva__enum($_[0],\@e); }; }
sub cmd_mark {
$mark= 1;
$nl->{X}= can(\&cva_len);
$nl->{Y}= can(\&cva_len);
$nl->{A}= cano(\&cva_ang, undef);
-}
+dv('cmd_abs ','$nl',$nl,'\\%loc',\%loc);
+}
+
+sub evreff ($) {
+ my ($pfx) = @_;
+ $pfx . ($pfx =~ m/\}$|\]$/ ? '' : '->');
+}
+sub evr ($) {
+ my ($v) = @_;
+ return $v if $v !~ m/\W/ && $v =~ m/[A-Z]/ && $v =~ m/^[a-z_]/i;
+ return $v if $v eq ($v+0.0);
+ $v =~ s/[\\\']/\\$&/g;
+ return "'$v'";
+}
+sub dv1 ($$$);
+sub dv1_kind ($$$$$$$) {
+ my ($pfx,$expr,$ref,$ref_exp,$ixfmt,$ixesfn,$ixmapfn) = @_;
+ my ($ix,$any);
+ return 0 if $ref ne $ref_exp;
+ $any=0;
+ foreach $ix (&$ixesfn) {
+ $any=1;
+ my ($v)= &$ixmapfn($ix);
+#print STDERR "dv1_kind($pfx,$expr,$ref,$ref_exp,$ixmapfn) ix=$ix v=$v\n";
+ dv1($pfx,$expr.sprintf($ixfmt,evr($ix)),$v);
+ }
+ if (!$any) {
+ printf DEBUG "%s%s= ".$ixfmt, $pfx, $expr, ' ';
+ }
+ 1;
+}
+sub dv1 ($$$) {
+ return unless $debug;
+ my ($pfx,$expr,$v) = @_;
+ $ref= ref $v;
+#print STDERR "dv1 >$pfx|$ref<\n";
+ if (!$ref) {
+ printf DEBUG "%s%s= %s\n", $pfx,$expr, evr($v);
+ return;
+ } elsif ($ref eq 'SCALAR') {
+ dv1($pfx, ($expr =~ m/^\$/ ? "\$$expr" : '${'.$expr.'}'), $$v);
+ return;
+ }
+ $expr.='->' unless $expr =~ m/\]$|\}$/;
+ return if dv1_kind($pfx,$expr,$ref,'ARRAY','[%s]',
+ sub { ($[ .. $#$v) },
+ sub { $v->[$_[0]] });
+ return if dv1_kind($pfx,$expr,$ref,'HASH','{%s}',
+ sub { sort keys %$v },
+ sub { $v->{$_[0]} });
+ printf DEBUG "%s%s is %s\n", $pfx, $expr, $ref;
+}
+
+sub dv {
+ my ($pfx,@l) = @_;
+ my ($expr,$v,$ref);
+ while (@l) {
+ ($expr,$v,@l)=@l;
+ dv1($pfx,$expr,$v);
+ }
+}
+
+sub loc_lin_comb ($$$) {
+ my ($a,$b,$p) = @_;
+ my ($q,$r) = 1.0-$p;
+ map { $r->{$_} = $q * $a->{$_} + $p * $b->{$_} } qw(X Y A);
+ dv("loc_lin_comb ",'$a',$a,'$b',$b,'$p',$p,'$r',$r);
+ return $r;
+}
+
+$psu_ulen= 4.5;
+$psu_edgelw= 0.5;
+$psu_ticklw= 0.1;
+$psu_ticksperu= 3;
+$psu_ticklen= 3.0;
+$psu_allwidth= 37.0/2;
+$psu_gauge= 9;
+$psu_sleeperlen= 17;
+$psu_sleeperlw= 15;
+$psu_raillw= 1.0;
+
+sub o ($) {
+ # fixme optional marking
+ print "$_[0]" or die $!;
+}
+
+sub o_path_begin () {
+ o(" newpath\n");
+ $o_path_verb= 'moveto';
+}
+sub o_path_point ($) {
+ my ($pt)=@_;
+ o(" $pt $o_path_verb\n");
+ $o_path_verb= 'lineto';
+}
+sub o_path_stroke ($) {
+ my ($width)=@_;
+ o(" $width setlinewidth stroke\n");
+}
+
+sub o_line ($$$) {
+ my ($a,$b,$width)=@_;
+ o_path_begin();
+ o_path_point($a);
+ o_path_point($b);
+ o_path_stroke($width);
+}
+
+sub psu_coords ($$$) {
+ my ($ends,$inunit,$across)=@_;
+ # $ends->[0]{X} etc.; $inunit 0 to 1 (but go to 1.5);
+ # $across in mm, +ve to right.
+ my (%ea_zo);
+ $ea_zo{X}=$ea_zo{Y}=0;
+ foreach $zo (qw(0 1)) {
+ $prop= $zo ? $inunit : (1.0 - $inunit);
+ $ea_zo{X} += $prop * ($ends->[$zo]{X} - $across * sin($ends->[0]{A}));
+ $ea_zo{Y} += $prop * ($ends->[$zo]{Y} - $across * cos($ends->[0]{A}));
+ }
+# dv("psu_coords ", '$ends',$ends, '$inunit',$inunit, '$across',$across,
+# '\\%ea_zo', \%ea_zo);
+ return $ea_zo{X}." ".$ea_zo{Y};
+}
+
+sub parametric_segment ($$$$$) {
+ my ($endstatuses,$p0,$p1,$lenperp,$calcfn) = @_;
+ # makes $p (global) go from $p0 to $p1 ($p1>$p0)
+ # $ends is II, SI, IS, SS (I=actual lineobj end, S=in mid of lineobj)
+ # $lenperp is the length of one unit p, ie the curve
+ # must have a uniform `density' in parameter space
+ # $calcfn is invoked with $p set and should return a loc
+ # (ie, ref to X =>, Y =>, A =>).
+ my ($pa,$pb,@ends,$side,$ppu,$e,$v,$tick);
+ $ppu= $psu_ulen/$lenperp;
+ my ($railctr)=($psu_gauge + $psu_raillw)*0.5;
+ my ($tickend)=($psu_allwidth - $psu_ticklen);
+ my ($tickpitch)=($psu_ulen / $psu_ticksperu);
+ my ($sleeperctr)=($psu_ulen*0.5);
+ my ($sleeperend)=($psu_sleeperlen*0.5);
+print DEBUG "ps $p0 $p1\n";
+ for ($pa= $p0; $pa<$p1; $pa=$pb) {
+ $pb= $pa + $ppu;
+ $p= $pa; $ends[0]= @ends ? $ends[1] : &$calcfn;
+ $p= $pb; $ends[1]= &$calcfn;
+print DEBUG "pa $pa $ends[0]{X} $ends[0]{Y} $ends[0]{A}\n";
+print DEBUG "pb $pb $ends[1]{X} $ends[1]{Y} $ends[1]{A}\n";
+ $e= $pb<=$p1 ? 1.0 : ($p1-$pa)/$ppu;
+ o(" gsave\n");
+ o_path_begin();
+ o_path_point(psu_coords(\@ends,0,-$psu_allwidth));
+ o_path_point(psu_coords(\@ends,0,$psu_allwidth));
+ o_path_point(psu_coords(\@ends,$e,$psu_allwidth));
+ o_path_point(psu_coords(\@ends,$e,-$psu_allwidth));
+ o(" closepath clip\n");
+ foreach $side qw(-1 1) {
+ o_line(psu_coords(\@ends,0,$side*$psu_allwidth),
+ psu_coords(\@ends,1.5,$side*$psu_allwidth),
+ $psu_edgelw);
+ o_line(psu_coords(\@ends,0,$side*$railctr),
+ psu_coords(\@ends,1.5,$side*$railctr),
+ $psu_raillw);
+ for ($tick=0; $tick<1.5; $tick+=$tickpitch/$psu_ulen) {
+ o_line(psu_coords(\@ends,$tick,$side*$psu_allwidth),
+ psu_coords(\@ends,$tick,$side*$tickend),
+ $psu_ticklw);
+ }
+ }
+ o_line(psu_coords(\@ends,$sleeperctr,-$sleeperend),
+ psu_coords(\@ends,$sleeperctr,+$sleeperend),
+ $psu_sleeperlw);
+ o(" grestore\n");
+ }
+}
sub cmd_extend {
+ my ($from,$to);
$from= can(\&cva_idex);
$to= can(\&cva_idnew);
+ printf DEBUG "%s %s\n", join("|", keys %$from), join("|", values %$from);
die "no ang" unless defined $from->{A};
- $how= can(cva_enum(qw(len upto ang)));
+ $how= can(cvam_enum(qw(len upto ang)));
if ($how eq 'len') { $len= can(\&cva_len); }
elsif ($how eq 'ang') { $ang= can(\&cva_ang); }
elsif ($how eq 'upto') { $upto= can(\&cva_id); }
$radius= cano(\&cva_ang, 'Inf'); # +ve is right hand bend
if ($radius eq 'Inf') {
+ print DEBUG "extend inf $len\n";
if ($how eq 'ang') { die "len of straight spec by angle"; }
if ($how eq 'upto') {
$len= ($upto->{X} - $from->{X}) * cos($from->{a})
$to->{X}= $from->{X} + $len * cos($from->{A});
$to->{Y}= $from->{Y} + $len * sin($from->{A});
$to->{A}= $from->{A};
- # fixme mark
+ parametric_segment(II, 0.0, 1.0, $len, sub {
+ loc_lin_comb($from, $to, $p);
+ });
} else {
- $signum= $radius / abs($radisu);
+ $signum= $radius / abs($radius);
$ctr->{X}= $from->{X} + $radius * sin($from->{A});
$ctr->{Y}= $from->{Y} - $radius * cos($from->{A});
if ($how eq 'upto') {
$to->{A}= $beta;
$to->{X}= $ctr->{X} + $radius * cos($beta);
$to->{Y}= $ctr->{Y} - $radius * sin($beta);
-
-
+# parametric_segment(0, II, $radius);
+ }
die "point only" unless defined $from->{A};
join($from,$to);
}
sub cmd__do {
-
-
-sub cmd_str_asfar {
- $from= can(\&cva_idex);
- $to= can(\&cva_idex);
-
+ $cmd= can(\&cva_cmd);
+ &{ "cmd_$cmd" };
+}
+
+$ptscale= 72/25.4 * 0.1;
+
+o("%!\n".
+ " $ptscale $ptscale scale\n");
while (<>) {
next if m/^\s*\#/;
@al= split /\s+/, $_;
next unless @al;
$mark= 0;
- $cmd= can(\&cva_cmd);
-shift @al; $cmd =~ y/-_/_-/;
- &{ "cmd_$cmd" };
+ cmd__do();
}
+dv('','\\%loc',\%loc);