Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.postscript > #3124 > unrolled thread
| Started by | luser droog <luser.droog@gmail.com> |
|---|---|
| First post | 2017-06-21 22:50 -0700 |
| Last post | 2017-07-26 10:29 -0700 |
| Articles | 14 — 2 participants |
Back to article view | Back to comp.lang.postscript
Control Structures luser droog <luser.droog@gmail.com> - 2017-06-21 22:50 -0700
Re: Control Structures Carlos <carlos@cvkm.cz> - 2017-06-23 00:28 +0200
Re: Control Structures luser droog <luser.droog@gmail.com> - 2017-06-23 00:01 -0700
Re: Control Structures luser droog <luser.droog@gmail.com> - 2017-06-28 10:57 -0700
Re: Control Structures luser droog <luser.droog@gmail.com> - 2017-06-28 21:25 -0700
Re: Control Structures luser droog <luser.droog@gmail.com> - 2017-06-30 03:05 -0700
Re: Control Structures (Ordered Dictionaries) luser droog <luser.droog@gmail.com> - 2017-07-03 20:01 -0700
Binding - Re: Control Structures (Ordered Dictionaries) Carlos <carlos@cvkm.cz> - 2017-07-04 17:21 +0200
Type-checked function arguments - Re: Binding - Re: Control Structures (Ordered Dictionaries) luser droog <luser.droog@gmail.com> - 2017-07-09 10:44 -0700
Re: Type-checked function arguments - Re: Binding - Re: Control Structures (Ordered Dictionaries) luser droog <luser.droog@gmail.com> - 2017-07-10 09:33 -0700
Re: Type-checked function arguments - Re: Binding - Re: Control Structures (Ordered Dictionaries) luser droog <luser.droog@gmail.com> - 2017-07-15 20:00 -0700
Re: Type-checked function arguments - Re: Binding - Re: Control Structures (Ordered Dictionaries) luser droog <luser.droog@gmail.com> - 2017-07-25 23:07 -0700
Re: Type-checked function arguments - Re: Binding - Re: Control Structures (Ordered Dictionaries) Carlos <carlos@cvkm.cz> - 2017-07-26 16:41 +0200
Re: Type-checked function arguments - Re: Binding - Re: Control Structures (Ordered Dictionaries) luser droog <luser.droog@gmail.com> - 2017-07-26 10:29 -0700
| From | luser droog <luser.droog@gmail.com> |
|---|---|
| Date | 2017-06-21 22:50 -0700 |
| Subject | Control Structures |
| Message-ID | <2f65c3d3-60f0-48d6-af25-7af2964b9254@googlegroups.com> |
In the midst of the coding for the Parser Combinators, I think
I've come up with a nice way to implement functions that behave
like control structures. Notwithstanding potential problems
with redefinition of needed operators which Carlos' map function
solves, this way provides fairly readable control structures
with nice features.
Starting with curry and compose we get a pretty readable
definition for adding local variables to functions. This is a
recapitulation of the code from the thread "func def".
/curry { /exec cvx 3 array astore cvx } def
/combine { /exec cvx exch 1 index 4 array astore cvx } de
/reverse-array { [ exch dup length 1 sub -1 0 { 2 copy get 3 1 roll pop } for pop ] } def
/args { dup length dict begin {exch def} forall } def
/func { exch reverse-array //args curry exch combine {end} combine } def
%/f {x y z}{ x y add z mul } func def 1 2 3 f = %=> 9
Then we can write a new control structure by adding code
to an existing control structure. The one I keep writing
lately is /fortuple which calls the proc with slices of
the array (or string) instead of the elements. As a nice
side-effect, if you use an empty proc {} it just slices
up the array (or string).
/fortuple {arr n proc}{
({
0 //n //arr length 1 sub {
//arr exch //n getinterval
//proc exec
} for
}) cvx exec exec
} func def
The 'cvx' makes the string executable. Calling 'exec' on
that yields the executable procedure scanned from the
string with all //immediate names loaded at that point.
Then one more 'exec' to execute the procedure.
The advantage is the execution can be made invisible to
the /proc. By defining the currentdict as /mydict and
sandwiching the '//proc exec' with 'end ... //mydict begin'
the /proc will see none of our bookkeeping either on
the stack or the dict stack.
/fortuple {arr n proc}{
/mydict currentdict def
({
0 //n //arr length 1 sub {
//arr exch //n getinterval
end
//proc exec
//mydict begin
} for
}) cvx exec exec
} func def
The final complication arises if the /proc wants to call
'exit' to terminate the loop early. We're mostly set up
to handle this. The looping happens inside a single
built-in looping construct, 'for'. So 'exit' will exit
this single loop. Unfortunately this bypasses the
'//mydict begin' that we need since the 'func' wrapper
adds an 'end' to the end of the function.
/fortuple {arr n proc}{
/mydict currentdict def
({
0 //n //arr length 1 sub {
{
//arr exch //n getinterval
end
//proc exec
//mydict begin
} stopped {
$error /errorname get /invalidexit eq
{ 1 dict begin exit }{ stop } ifelse
} if
} for
}) cvx exec exec
} func def
This last ugly step is the next hurdle I think.
It would be nice to tuck this part under the rug somehow
and make it look more like the first or second version.
A variation of 'func' which did not append an 'end' would
allow us to remove the /mydict stuff since we could put
'end' before the final 'exec' and remove the dict early.
That would work since all the variables are instantiated
in the generated procedure and the /dict is no longer
necessary.
This version requires that the array (or string) length
be divisible by n. A different version from here,
https://stackoverflow.com/documentation/postscript/6199/error-handling/21486/re-throwing-errors#t=201706220535518165373
delivers any smaller remainder as a last iteration.
That version was written before I realized how nice the
'({...}) cvx exec exec' construct was. The 'cvx exec'
could also be written 'token pop exch pop' which will
do the same thing. But 'cvx exec' is shorter.
% alternate version
% array n proc . -
% Like `forall` but delivers length=n subsequences produced by getinterval
/fortuple { 4 dict begin
0 {offset proc n arr} {exch def} forall
/arr load length n idiv
{
{
/arr load offset n getinterval
[ /proc load currentdict end /begin cvx ] cvx exec
/offset offset n add def
} stopped {
$error /errorname get /invalidexit eq
{ 1 dict begin exit }{ stop } ifelse
} if
} repeat
end
} def
%[ 0 1 10 {} for ] 3 {} fortuple pstack clear ()=
[toc] | [next] | [standalone]
| From | Carlos <carlos@cvkm.cz> |
|---|---|
| Date | 2017-06-23 00:28 +0200 |
| Message-ID | <20170623002801.61560cd4@samara.DOMA> |
| In reply to | #3124 |
[luser droog <luser.droog@gmail.com>, 2017-06-21 22:50]
> In the midst of the coding for the Parser Combinators, I think
> I've come up with a nice way to implement functions that behave
> like control structures. Notwithstanding potential problems
> with redefinition of needed operators which Carlos' map function
> solves, this way provides fairly readable control structures
> with nice features.
>
> Starting with curry and compose we get a pretty readable
> definition for adding local variables to functions. This is a
> recapitulation of the code from the thread "func def".
>
>
>
> /curry { /exec cvx 3 array astore cvx } def
> /combine { /exec cvx exch 1 index 4 array astore cvx } de
> /reverse-array { [ exch dup length 1 sub -1 0 { 2 copy get 3 1 roll pop } for
> pop ] } def /args { dup length dict begin {exch def} forall } def
> /func { exch reverse-array //args curry exch combine {end} combine } def
> %/f {x y z}{ x y add z mul } func def 1 2 3 f = %=> 9
>
>
> Then we can write a new control structure by adding code
> to an existing control structure. The one I keep writing
> lately is /fortuple which calls the proc with slices of
> the array (or string) instead of the elements. As a nice
> side-effect, if you use an empty proc {} it just slices
> up the array (or string).
>
> /fortuple {arr n proc}{
> ({
> 0 //n //arr length 1 sub {
> //arr exch //n getinterval
> //proc exec
> } for
> }) cvx exec exec
> } func def
>
> The 'cvx' makes the string executable. Calling 'exec' on
> that yields the executable procedure scanned from the
> string with all //immediate names loaded at that point.
> Then one more 'exec' to execute the procedure.
>
> The advantage is the execution can be made invisible to
> the /proc. By defining the currentdict as /mydict and
> sandwiching the '//proc exec' with 'end ... //mydict begin'
> the /proc will see none of our bookkeeping either on
> the stack or the dict stack.
>
> /fortuple {arr n proc}{
> /mydict currentdict def
> ({
> 0 //n //arr length 1 sub {
> //arr exch //n getinterval
> end
> //proc exec
> //mydict begin
> } for
> }) cvx exec exec
> } func def
I love how simple and understandable it is. I still don't like parsing the
string every time, though. I know it's an irrational dislike, it's probably even
faster than replacing the "variables" by hand, but...
It's also more cumbersome to edit :)
> The final complication arises if the /proc wants to call
> 'exit' to terminate the loop early. We're mostly set up
> to handle this. The looping happens inside a single
> built-in looping construct, 'for'. So 'exit' will exit
> this single loop. Unfortunately this bypasses the
> '//mydict begin' that we need since the 'func' wrapper
> adds an 'end' to the end of the function.
Start the string with "({ end " and finish it with " } for //mydict begin })"?
>
> /fortuple {arr n proc}{
> /mydict currentdict def
> ({
> 0 //n //arr length 1 sub {
> {
> //arr exch //n getinterval
> end
> //proc exec
> //mydict begin
> } stopped {
> $error /errorname get /invalidexit eq
> { 1 dict begin exit }{ stop } ifelse
> } if
> } for
> }) cvx exec exec
> } func def
>
>
> This last ugly step is the next hurdle I think.
> It would be nice to tuck this part under the rug somehow
> and make it look more like the first or second version.
> A variation of 'func' which did not append an 'end' would
> allow us to remove the /mydict stuff since we could put
> 'end' before the final 'exec' and remove the dict early.
> That would work since all the variables are instantiated
> in the generated procedure and the /dict is no longer
> necessary.
>
> This version requires that the array (or string) length
> be divisible by n. A different version from here,
> https://stackoverflow.com/documentation/postscript/6199/error-handling/21486/re-throwing-errors#t=201706220535518165373
> delivers any smaller remainder as a last iteration.
> That version was written before I realized how nice the
> '({...}) cvx exec exec' construct was. The 'cvx exec'
> could also be written 'token pop exch pop' which will
> do the same thing. But 'cvx exec' is shorter.
>
> % alternate version
>
> % array n proc . -
> % Like `forall` but delivers length=n subsequences produced by getinterval
> /fortuple { 4 dict begin
> 0 {offset proc n arr} {exch def} forall
> /arr load length n idiv
> {
> {
> /arr load offset n getinterval
> [ /proc load currentdict end /begin cvx ] cvx exec
> /offset offset n add def
> } stopped {
> $error /errorname get /invalidexit eq
> { 1 dict begin exit }{ stop } ifelse
> } if
> } repeat
> end
> } def
>
> %[ 0 1 10 {} for ] 3 {} fortuple pstack clear ()=
[toc] | [prev] | [next] | [standalone]
| From | luser droog <luser.droog@gmail.com> |
|---|---|
| Date | 2017-06-23 00:01 -0700 |
| Message-ID | <3c26cbff-0802-482a-a6d2-07b17781a1d4@googlegroups.com> |
| In reply to | #3125 |
On Thursday, June 22, 2017 at 5:28:02 PM UTC-5, Carlos wrote:
> [luser droog <luser.droog@gmail.com>, 2017-06-21 22:50]
> > The 'cvx' makes the string executable. Calling 'exec' on
> > that yields the executable procedure scanned from the
> > string with all //immediate names loaded at that point.
> > Then one more 'exec' to execute the procedure.
> >
> > The advantage is the execution can be made invisible to
> > the /proc. By defining the currentdict as /mydict and
> > sandwiching the '//proc exec' with 'end ... //mydict begin'
> > the /proc will see none of our bookkeeping either on
> > the stack or the dict stack.
> >
> > /fortuple {arr n proc}{
> > /mydict currentdict def
> > ({
> > 0 //n //arr length 1 sub {
> > //arr exch //n getinterval
> > end
> > //proc exec
> > //mydict begin
> > } for
> > }) cvx exec exec
> > } func def
>
> I love how simple and understandable it is. I still don't like parsing the
> string every time, though. I know it's an irrational dislike, it's probably even
> faster than replacing the "variables" by hand, but...
>
> It's also more cumbersome to edit :)
Yes. As Dr. Gibarian says, "There are no answers, only choices."
Parsing the string each time does lead to wanting to shorten
the identifiers to speed up the parse time. Perhaps the operators
could be binary system name tokens. The variables could be single
letters. But then the balance is pulled very far from readability.
Another option is pulling the 'for' out of the dynamic procedure
and all of its integer parameters. We could generate just the loop
body to reduce the amount of stuff being scanned.
/fortuple {arr n proc}{
0 n /arr load length 1 sub
({ //arr exch //n getinterval }) cvx exec end
/proc load combine
for
1 dict begin
} func def
Now the string is short enough, it could even be done 'by hand'
without being too unweildy.
[ /arr load /exch cvx n /getinterval cvx ] cvx end
Whereas, the whole thing could be done 'by hand', as well.
/fortuple { % arr n proc
3 1 roll % proc arr n
0 exch 2 index length 1 sub % proc arr 0 n arr#-1
[
/arr load /exch cvx n /getinterval cvx % p a 0 n #-1 [ ...
counttomark 6 add -1 roll aload pop % a 0 n #-1 [... proc]
] cvx
for
} def
Now, I still dislike /this/, for perhaps irrational reasons.
I don't like the extra /s on everything that this technique requires.
And all the counting and tracking the stack. I can do it after
much practice. But the more readable techniques are more writable, too,
I think. (I'm /pretty sure/ I counted everything correctly, but I did
not test it. 'counttomark' is an oddball, and I probably should have
looked it up instead of reasoning from memory.)
I've experimented before with a local arguments method that patches the
arguments into the procedure body from a list of 'coordinates'.
But the way I did it makes the function not re-entrant since it modifies
the single copy of the procedure array.
> > The final complication arises if the /proc wants to call
> > 'exit' to terminate the loop early. We're mostly set up
> > to handle this. The looping happens inside a single
> > built-in looping construct, 'for'. So 'exit' will exit
> > this single loop. Unfortunately this bypasses the
> > '//mydict begin' that we need since the 'func' wrapper
> > adds an 'end' to the end of the function.
>
> Start the string with "({ end " and finish it with " } for //mydict begin })"?
>
Yes. Thank you. Much better.
[toc] | [prev] | [next] | [standalone]
| From | luser droog <luser.droog@gmail.com> |
|---|---|
| Date | 2017-06-28 10:57 -0700 |
| Message-ID | <73236ae3-bc2c-4fea-b4a5-bf43bf4d9060@googlegroups.com> |
| In reply to | #3127 |
Rewrote it all again. This one uses binary tokens to construct a template
string that will parse quickly. I also re-wrote /reverse without a for loop.
No real advantage in doing that, I suppose. But fun practice.
The commented lines show "readable" postscript, then the encoded system
names in decimal (as I looked them up in PLRM Appendix F), then the
encodings in hex. Then there's a hex string which represents the same
code as the "readable" version.
%!
<<
/curry { [ 3 1 roll {}forall ] cvx }
/compose { [ 3 1 roll 2 array astore { {}forall }forall ] cvx }
/reverse { dup xcheck exch [ exch reverse-rest ] exch { cvx } if }
/reverse-rest { dup length 0 gt {
dup rest /reverse-rest cvx 3 -1 roll /first cvx 4 array astore cvx exec
}{ pop } ifelse }
/first { 0 get }
/rest { 1 1 index length 1 sub getinterval }
/args-begin { dup length dict begin { exch def } forall }
/func { exch reverse /args-begin load curry
exch compose { end } compose }
/ps{pstack/ =}/pq{ps quit}
>> begin
%errordict/undefined{pstack} 3 copy pop get compose put
%errordict/rangecheck{pstack} 3 copy pop get compose put
/fortuple {a n p}{
/# currentdict def
%({ end 0 //n //a length 1 sub { //a exch //n getinterval//p exec } for //# begin }) cvx exec
%d 57 136 110 97 98 169 62 76 63 72 13
%x 39 88 6E 61 62 A9 3E 4C 3F 48 0D
<7b 9239 30 2f2f6e 2f2f61 9262 31 92a9 7b 2f2f61 923e 2f2f6e 924c 2f2f70 923f 7d 9248 2f2f23 920d 7d> cvx exec
exec
} func def
[1 2 3 4] 2 {} fortuple
pq
$ gsnd -q struct.ps
[3 4]
[1 2]
[toc] | [prev] | [next] | [standalone]
| From | luser droog <luser.droog@gmail.com> |
|---|---|
| Date | 2017-06-28 21:25 -0700 |
| Message-ID | <8daef6c8-92d4-4de1-b2db-5c71d8f68cfb@googlegroups.com> |
| In reply to | #3129 |
On Wednesday, June 28, 2017 at 12:57:22 PM UTC-5, luser droog wrote:
> Rewrote it all again. This one uses binary tokens to construct a template
> string that will parse quickly. I also re-wrote /reverse without a for loop.
> No real advantage in doing that, I suppose. But fun practice.
>
> The commented lines show "readable" postscript, then the encoded system
> names in decimal (as I looked them up in PLRM Appendix F), then the
> encodings in hex. Then there's a hex string which represents the same
> code as the "readable" version.
>
Further refinement. 'dup ==' showed me a nicer way to write the hex string.
And doing the 'func' manipulation by hand allows the 'end' to be placed
where we want. Then the string template can be just the body of the
for-loop.
Substitutions take 3 characters. Punctuation takes its usual 1. Operator
names in the source take 5 characters in the source, but only 2 once the
scanner produces the internal string object which then gets scanned when
called.
<<
/curry { [ 3 1 roll {}forall ] cvx }
/compose { [ 3 1 roll 2 array astore { {}forall }forall ] cvx }
/reverse { dup xcheck exch [ exch reverse-rest ] exch { cvx } if }
/reverse-rest { dup length 0 gt {
dup rest /reverse-rest cvx 3 -1 roll /first cvx 4 array astore cvx exec
}{ pop } ifelse }
/first { 0 get }
/rest { 1 1 index length 1 sub getinterval }
/args-begin { dup length dict begin { exch def } forall }
/func { exch reverse /args-begin load curry
exch compose { end } compose }
/ps{pstack/ =}/pq{ps quit}/pc{ps clear}
>> begin
%errordict/undefined{pstack} 3 copy pop get compose put
%errordict/rangecheck{pstack} 3 copy pop get compose put
/fortuple {a n p}{
/# currentdict def
%({ end 0 //n //a length 1 sub { //a exch //n getinterval//p exec } for //# begin }) cvx exec
%d 57 136 110 97 98 169 62 76 63 72 13
%x 39 88 6E 61 62 A9 3E 4C 3F 48 0D
%<7b 9239 30 2f2f6e 2f2f61 9262 31 92a9 7b 2f2f61 923e 2f2f6e 924c 2f2f70 923f 7d 9248 2f2f23 920d 7d> cvx exec
({\22290//n//a\222b1\222\251{//a\222>//n\222L//p\222?}\222H//#\222\r}) cvx exec
exec
} func def
[1 2 3 4] 2 {} fortuple pc
/fortuple {a n p}reverse{args-begin
0 n /a load length 1 sub
% \222>=exch \222L=getinterval \222?=exec
({//a\222>//n\222L//p\222?}) cvx exec
end for
} curry def
[1 2 3 4] 2 {} fortuple pq
[toc] | [prev] | [next] | [standalone]
| From | luser droog <luser.droog@gmail.com> |
|---|---|
| Date | 2017-06-30 03:05 -0700 |
| Message-ID | <c4a927b0-6d18-46c0-b424-486915f7934a@googlegroups.com> |
| In reply to | #3130 |
On Wednesday, June 28, 2017 at 11:25:54 PM UTC-5, luser droog wrote:
> On Wednesday, June 28, 2017 at 12:57:22 PM UTC-5, luser droog wrote:
> > Rewrote it all again. This one uses binary tokens to construct a template
> > string that will parse quickly. I also re-wrote /reverse without a for loop.
> > No real advantage in doing that, I suppose. But fun practice.
> >
> > The commented lines show "readable" postscript, then the encoded system
> > names in decimal (as I looked them up in PLRM Appendix F), then the
> > encodings in hex. Then there's a hex string which represents the same
> > code as the "readable" version.
> >
>
> Further refinement. 'dup ==' showed me a nicer way to write the hex string.
> And doing the 'func' manipulation by hand allows the 'end' to be placed
> where we want. Then the string template can be just the body of the
> for-loop.
>
> Substitutions take 3 characters. Punctuation takes its usual 1. Operator
> names in the source take 5 characters in the source, but only 2 once the
> scanner produces the internal string object which then gets scanned when
> called.
>
A little more playing with it. And I think my opinion may have changed.
I rewrote the short loop body in regular array syntax.
%({//a\222>//n\222L//p\222?}) cvx exec % \222>=exch \222L=getinterval \222?=exec
[/a load /exch cvx n /getinterval cvx /p load /exec cvx ] cvx
Then, I thought "it could easily do 'load' instead of 'cvx' and then
do '{load}forall'. So I added /map to the toolset. (And /reduce because
/map gets lonely without him.) Then the line can be:
{a exch n getinterval p exec} {load} map
(I also have /map do the 'cvx' if the source was 'xcheck' so that
factors out too.)
Then a little more elaboration and it's almost the same as Carlos's
solution. :)
I imagine there may be several different rules by which one might
want substitutions to occur. Perhaps checking the 'type' for /nametype.
This case doesn't have any types but names, but calling 'load' on
say a number will only succeed if that number is 'def'ined. So a
more robust tool should be sensible about that.
$ cat struct.ps
<<
/curry { [ 3 1 roll {}forall ] cvx }
/compose { [ 3 1 roll 2 array astore { {}forall }forall ] cvx }
/reverse { dup xcheck exch [ exch reverse-rest ] exch { cvx } if }
/reverse-rest { dup length 0 gt {
dup rest /reverse-rest cvx 3 -1 roll /first cvx 4 array astore cvx exec
}{ pop } ifelse }
/first { 0 get }
/rest { 1 1 index length 1 sub getinterval }
/args-begin { dup length dict begin { exch def } forall }
/func { exch reverse /args-begin load curry
exch compose { end } compose }
/map { 1 index xcheck 3 1 roll [ 3 1 roll forall ] exch { cvx } if }
/reduce { exch dup first exch rest 3 -1 roll forall }
/ps{pstack/ =}/pq{ps quit}/pc{ps clear}
>> begin
%errordict/undefined{pstack} 3 copy pop get compose put
%errordict/rangecheck{pstack} 3 copy pop get compose put
/fortuple {a n p}{
/# currentdict def
%({ end 0 //n //a length 1 sub { //a exch //n getinterval//p exec } for //# begin }) cvx exec
%d 57 136 110 97 98 169 62 76 63 72 13
%x 39 88 6E 61 62 A9 3E 4C 3F 48 0D
%<7b 9239 30 2f2f6e 2f2f61 9262 31 92a9 7b 2f2f61 923e 2f2f6e 924c 2f2f70 923f 7d 9248 2f2f23 920d 7d> cvx exec
({\22290//n//a\222b1\222\251{//a\222>//n\222L//p\222?}\222H//#\222\r}) cvx exec
exec
} func def
[1 2 3 4] 2 {} fortuple pc
/instantiate { { currentdict 1 index known {load} if } map } def % { {load} map } def
% { { dup xcheck { load } if } map }
/fortuple {a n p}reverse{args-begin
0 n /a load length 1 sub
%({//a\222>//n\222L//p\222?}) cvx exec % \222>=exch \222L=getinterval \222?=exec
%[/a load /exch cvx n /getinterval cvx /p load /exec cvx ] cvx
{a exch n getinterval p exec} instantiate
end bind for
} curry def
[1 2 3 4] 2 {} fortuple pq
[toc] | [prev] | [next] | [standalone]
| From | luser droog <luser.droog@gmail.com> |
|---|---|
| Date | 2017-07-03 20:01 -0700 |
| Subject | Re: Control Structures (Ordered Dictionaries) |
| Message-ID | <0de6e3e8-33c0-4d6a-9c32-44a880509c84@googlegroups.com> |
| In reply to | #3131 |
On Friday, June 30, 2017 at 5:06:01 AM UTC-5, luser droog wrote:
> On Wednesday, June 28, 2017 at 11:25:54 PM UTC-5, luser droog wrote:
> > On Wednesday, June 28, 2017 at 12:57:22 PM UTC-5, luser droog wrote:
> > > Rewrote it all again. This one uses binary tokens to construct a template
> > > string that will parse quickly. I also re-wrote /reverse without a for loop.
> > > No real advantage in doing that, I suppose. But fun practice.
> > >
> > > The commented lines show "readable" postscript, then the encoded system
> > > names in decimal (as I looked them up in PLRM Appendix F), then the
> > > encodings in hex. Then there's a hex string which represents the same
> > > code as the "readable" version.
> > >
> >
> > Further refinement. 'dup ==' showed me a nicer way to write the hex string.
> > And doing the 'func' manipulation by hand allows the 'end' to be placed
> > where we want. Then the string template can be just the body of the
> > for-loop.
> >
> > Substitutions take 3 characters. Punctuation takes its usual 1. Operator
> > names in the source take 5 characters in the source, but only 2 once the
> > scanner produces the internal string object which then gets scanned when
> > called.
> >
>
> A little more playing with it. And I think my opinion may have changed.
> I rewrote the short loop body in regular array syntax.
>
> %({//a\222>//n\222L//p\222?}) cvx exec % \222>=exch \222L=getinterval \222?=exec
> [/a load /exch cvx n /getinterval cvx /p load /exec cvx ] cvx
>
> Then, I thought "it could easily do 'load' instead of 'cvx' and then
> do '{load}forall'. So I added /map to the toolset. (And /reduce because
> /map gets lonely without him.) Then the line can be:
>
> {a exch n getinterval p exec} {load} map
>
> (I also have /map do the 'cvx' if the source was 'xcheck' so that
> factors out too.)
>
> Then a little more elaboration and it's almost the same as Carlos's
> solution. :)
>
> I imagine there may be several different rules by which one might
> want substitutions to occur. Perhaps checking the 'type' for /nametype.
> This case doesn't have any types but names, but calling 'load' on
> say a number will only succeed if that number is 'def'ined. So a
> more robust tool should be sensible about that.
>
>
I replaced the >> operator with code that creates a special /.name_order
array definition which grabs all the keys in order. And a 'control structure'
called /foreach which works like /forall but gives the key/value pairs
according to the /.name_order definition.
Making new 'def'initions does not currently update the list at all.
But for purposes like the parser combinators, this will relieve the need
to sort the keys to iterate the dictionary in some kind of sensible order.
I tried using /fortuple, and as I read the commented-out line, it still
seems to me that it should work. But it trips an /invalidaccess error in
'def' and the stack has some weird things like /IdiomRecognition on it.
So instead, I wrote another recursion loop to grab the keys from the array.
And, recursion may be another viable alternative to consider when you need
a crazy loop. ...
$ cat struct.ps
<<
/first { 0 get }
/rest { 1 1 index length 1 sub getinterval }
/map { 1 index xcheck 3 1 roll [ 3 1 roll forall ] exch { cvx } if }
/reduce { exch dup first exch rest 3 -1 roll forall }
/curry { [ 3 1 roll {}forall ] cvx }
/compose { 2 array astore cvx { {}forall } map }
/reverse { dup xcheck exch [ exch reverse-rest ] exch { cvx } if }
/reverse-rest { dup length 0 gt { dup rest /reverse-rest cvx 3 -1 roll /first cvx 4 array astore cvx exec }{ pop } ifelse }
/args-begin { dup length dict begin { exch def } forall }
/func { exch reverse /args-begin load curry exch compose { end } compose }
/ps{pstack/ =}/pq{ps quit}/pc{ps clear}
>> begin
%errordict/undefined{pstack} 3 copy pop get compose put
%errordict/rangecheck{pstack} 3 copy pop get compose put
errordict/invalidaccess{pstack} 3 copy pop get compose put
/fortuple {a n p}{
/# currentdict def
%({ end 0 //n //a length 1 sub { //a exch //n getinterval//p exec } for //# begin }) cvx exec
%d 57 136 110 97 98 169 62 76 63 72 13
%x 39 88 6E 61 62 A9 3E 4C 3F 48 0D
%<7b 9239 30 2f2f6e 2f2f61 9262 31 92a9 7b 2f2f61 923e 2f2f6e 924c 2f2f70 923f 7d 9248 2f2f23 920d 7d> cvx exec
({\22290//n//a\222b1\222\251{//a\222>//n\222L//p\222?}\222H//#\222\r}) cvx exec
exec
} func def
%[1 2 3 4] 2 {} fortuple pc
/instantiate { { currentdict 1 index known {load} if } map } def % { {load} map } def
% { { dup xcheck { load } if } map }
/fortuple {a n p}reverse{args-begin
0 n /a load length 1 sub
%({//a\222>//n\222L//p\222?}) cvx exec % \222>=exch \222L=getinterval \222?=exec
%[/a load /exch cvx n /getinterval cvx /p load /exec cvx ] cvx
{a exch n getinterval p exec} instantiate
end bind for
} curry def
%[1 2 3 4] 2 {} fortuple pq
/first2 { dup 0 get exch 1 get } def
/rest2 { 2 1 index length 2 sub getinterval } def
/keys { dup length 0 eq { pop }{ dup first2 pop exch rest2 keys } ifelse } def
(>>){ counttomark dup 1 add copy array astore exch pop %ps
%mark exch 2 { aload pop pop } fortuple ] exch pop pq
mark exch keys ] /.name_order exch
counttomark 2 idiv dup dict begin { def } repeat pop currentdict end } def
/foreach {d p} {
d /.name_order get
{ d 1 index get p exec } instantiate
forall
} func def
<<
/a 1
/b 2
/c 3
/d 4
/e 5
/f 6
/g 7
/h 8
/i 9
/j 10
/k 11
/l 12
/m 13
>> dup ===
{} foreach pq
Norah@laptop ~
$ gsnd -q struct.ps
<< /g 7 /i 9 /b 2 /k 11 /d 4 /m 13 /f 6 /h 8 /a 1 /j 10 /c 3 /l 12 /e 5 /.name_order [/a /b /c /d /e /f /g /h /i /j /k /l /m] >>
13
/m
12
/l
11
/k
10
/j
9
/i
8
/h
7
/g
6
/f
5
/e
4
/d
3
/c
2
/b
1
/a
[toc] | [prev] | [next] | [standalone]
| From | Carlos <carlos@cvkm.cz> |
|---|---|
| Date | 2017-07-04 17:21 +0200 |
| Subject | Binding - Re: Control Structures (Ordered Dictionaries) |
| Message-ID | <20170704172151.27a32f78@samara.DOMA> |
| In reply to | #3135 |
[luser droog <luser.droog@gmail.com>, 2017-07-03 20:01] > I tried using /fortuple, and as I read the commented-out line, it still > seems to me that it should work. But it trips an /invalidaccess error in > 'def' and the stack has some weird things like /IdiomRecognition on it. "Instantiate" first replaces the placeholders and then binds. Maybe it's related. In any case, it should be the other way around; if "a" happens to be executable, you don't want bind trying to do idiom replacements on it (and probably neither on "p"). --
[toc] | [prev] | [next] | [standalone]
| From | luser droog <luser.droog@gmail.com> |
|---|---|
| Date | 2017-07-09 10:44 -0700 |
| Subject | Type-checked function arguments - Re: Binding - Re: Control Structures (Ordered Dictionaries) |
| Message-ID | <c5007832-5be8-4e86-b2ec-142251fc70e0@googlegroups.com> |
| In reply to | #3136 |
On Tuesday, July 4, 2017 at 10:21:53 AM UTC-5, Carlos wrote:
> [luser droog <luser.droog@gmail.com>, 2017-07-03 20:01]
> > I tried using /fortuple, and as I read the commented-out line, it still
> > seems to me that it should work. But it trips an /invalidaccess error in
> > 'def' and the stack has some weird things like /IdiomRecognition on it.
>
> "Instantiate" first replaces the placeholders and then binds. Maybe it's
> related. In any case, it should be the other way around; if "a" happens to be
> executable, you don't want bind trying to do idiom replacements on it (and
> probably neither on "p").
> --
Thanks. I'll leave 'bind' alone until I can read up on it. I'm not up-to-date
on the level 2 (and beyond) aspects.
With the ordered dictionaries, we now have a nice syntax and mechanism for
declaring arguments with types. So here's the draft. New stuff at the bottom.
$ cat struct.ps
<<
/first { 0 get }
/rest { 1 1 index length 1 sub getinterval }
/map { 1 index xcheck 3 1 roll [ 3 1 roll forall ] exch { cvx } if }
/reduce { exch dup first exch rest 3 -1 roll forall }
/curry { [ 3 1 roll {}forall ] cvx }
/compose { 2 array astore cvx { {}forall } map }
/reverse { dup xcheck exch [ exch reverse-rest ] exch { cvx } if }
/reverse-rest { dup length 0 eq { pop }{ dup rest /reverse-rest cvx 3 -1 roll /first cvx
4 array astore cvx exec } ifelse }
/args-begin { dup length dict begin { exch def } forall }
/func-begin { exch reverse /args-begin load curry exch compose }
/func { func-begin { end } compose }
/ps{pstack/ =}/pq{ps quit}/pc{ps clear}
>> begin
%errordict/typecheck{pstack} 3 copy pop get compose put
/instantiate {
{
{ { load } map } % Rule 0: load everything
{ { dup xcheck { load } if } map } % 1: load executables
{ { currentdict 1 index known { load } if } map } % 2: load current defs
{ { dup type/nametype eq { load } if } map } % 3: load names
{ { dup type/nametype eq 1 index xcheck and { load } if } map } % 4: load executable names
{ { dup type/nametype eq 1 index xcheck not and { load } if } map } % 5: load literal names
} exch get exec
} def
/fortuple {a n p}{
0 n /a load length 1 sub
{ /a exch /n getinterval /p exec } 2 instantiate
end for
} func-begin def
(>>){
counttomark dup 1 add copy array astore exch pop
mark exch 2 { aload pop pop } fortuple ] /.name_order exch
counttomark dup dict begin 2 idiv { def } repeat pop currentdict end
} def
/foreach {d p}{
d /.name_order known {
d /.name_order get { /d 1 index get /p exec } 2 instantiate
}{
d /p load
} ifelse
end forall
} func-begin def
{ << /a 1 /b 2 /c 3 /d 4 /e 5 /f 6 /g 7 /h 8 /i 9 /j 10 /k 11 /l 12 /m 13 >> dup ===
dup { pop =only ( ) print } foreach / =
{ exch pop =only ( ) print } foreach pq } pop
/typed-func { typed-func-begin { end } compose } def
/typed-func-begin { % name << /arg /typename >> { body }
exch dup /.name_order 2 copy get reverse put % name body args@
dup /.func_name 4 index put
{ dup /.name_order get length dict begin
dup /.func_name get /.func_name exch def
check-args } curry % name body {args@ ...begin... check-args}
exch % name {args->bool} body
{ /.func_name load cvx /typecheck signalerror } exch /ifelse cvx 3 array astore cvx
compose % name {args...body}
} def
/check-args { % ... <<args@>> -> bool
{ { check-arg } foreach } stopped
} def
/check-arg { % ? name typename
% count 3 lt { /stackunderflow } if
2 index type eq { % ! name
exch def
}{
stop
} ifelse
} def
/f << /a /integertype /b /realtype >> {
a b add
} typed-func def
1 2.0 f = / =
1.0 2 f
Norah@laptop ~
$ gsnd -q struct.ps
3.0
Error: /typecheck in f
Operand stack:
1.0 2 b
Execution stack:
%interp_exit .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- --nostringval-- false 1 %stopped_push 1983 1 3 %oparray_pop 1982 1 3 %oparray_pop 1966 1 3 %oparray_pop 1852 1 3 %oparray_pop --nostringval-- %errorexec_pop .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval--
Dictionary stack:
--dict:1201/1684(ro)(G)-- --dict:0/20(G)-- --dict:78/200(L)-- --dict:23/24(L)-- --dict:1/2(L)--
Current allocation mode is local
Current file position is 3159
GPL Ghostscript 9.19: Unrecoverable error, exit code 1
[toc] | [prev] | [next] | [standalone]
| From | luser droog <luser.droog@gmail.com> |
|---|---|
| Date | 2017-07-10 09:33 -0700 |
| Subject | Re: Type-checked function arguments - Re: Binding - Re: Control Structures (Ordered Dictionaries) |
| Message-ID | <9c61ec9b-2d69-4216-a18b-36c661c81dd4@googlegroups.com> |
| In reply to | #3137 |
On Sunday, July 9, 2017 at 12:44:25 PM UTC-5, luser droog wrote:
> On Tuesday, July 4, 2017 at 10:21:53 AM UTC-5, Carlos wrote:
> > [luser droog <luser.droog@gmail.com>, 2017-07-03 20:01]
> > > I tried using /fortuple, and as I read the commented-out line, it still
> > > seems to me that it should work. But it trips an /invalidaccess error in
> > > 'def' and the stack has some weird things like /IdiomRecognition on it.
> >
> > "Instantiate" first replaces the placeholders and then binds. Maybe it's
> > related. In any case, it should be the other way around; if "a" happens to be
> > executable, you don't want bind trying to do idiom replacements on it (and
> > probably neither on "p").
> > --
>
> Thanks. I'll leave 'bind' alone until I can read up on it. I'm not up-to-date
> on the level 2 (and beyond) aspects.
>
> With the ordered dictionaries, we now have a nice syntax and mechanism for
> declaring arguments with types. So here's the draft. New stuff at the bottom.
>
Reorganized. And added checking for stackunderflow.
$ cat struct.ps
%!
% func - a syntax and mechanism for named, type-checked arguments and local variables in
% function definitions.
<<
/func { 1 index type /dicttype eq { typed-func }{ simple-func } ifelse }
/simple-func { func-begin { end } compose }
/typed-func { typed-func-begin { end } compose }
/first { 0 get }
/rest { 1 1 index length 1 sub getinterval }
/map { 1 index xcheck 3 1 roll [ 3 1 roll forall ] exch { cvx } if }
/reduce { exch dup first exch rest 3 -1 roll forall }
/curry { [ 3 1 roll {}forall ] cvx }
/compose { 2 array astore cvx { {}forall } map }
/reverse { dup xcheck exch [ exch reverse-rest ] exch { cvx } if }
/reverse-rest {
dup length 0 eq { pop }{
dup rest /reverse-rest cvx 3 -1 roll /first cvx
4 array astore cvx exec
} ifelse
}
/args-begin { dup length dict begin { exch def } forall }
/func-begin { exch reverse /args-begin load curry exch compose }
/typed-func-begin { % name << /arg /typename >> { body }
exch dup /.name_order 2 copy get reverse put % name body args@
dup /.func_name 4 index put
{ dup /.name_order get length dict begin
dup /.func_name get /.func_name exch def
check-args } curry % name body {args@ ...begin... check-args}
exch % name {args->bool} body
{ /.func_name load cvx /.error_name load signalerror } exch /ifelse cvx 3 array astore cvx
compose % name {args...body}
}
/check-args { % ... <<args@>> -> bool True means fail
count 1 sub 1 index /.name_order get length lt {
/.error_name /stackunderflow cvx def
true
}{
{ { check-arg } foreach } stopped
} ifelse
}
/check-arg { % ? name typename
2 index type eq { % ! name
exch def
}{
/.error_name /typecheck cvx def
stop
} ifelse
}
/instantiate {
{
{ { load } map } % Rule 0: load everything
{ { dup xcheck { load } if } map } % 1: load executables
{ { currentdict 1 index known { load } if } map } % 2: load current defs
{ { dup type/nametype eq { load } if } map } % 3: load names
{ { dup type/nametype eq 1 index xcheck and { load } if } map } % 4: load executable names
{ { dup type/nametype eq 1 index xcheck not and { load } if } map } % 5: load literal names
} exch get exec }
(>>){
counttomark dup 1 add copy array astore exch pop
mark exch 2 { aload pop pop } fortuple ] /.name_order exch
counttomark dup dict begin 2 idiv { def } repeat pop currentdict end
}
/ps{pstack/ =}/pq{ps quit}/pc{ps clear}
>> begin
%errordict/typecheck{pstack} 3 copy pop get compose put
/fortuple {a n p}{
0 n /a load length 1 sub
{ /a exch /n getinterval /p exec } 2 instantiate
end for
} func-begin def
/foreach {d p}{
d /.name_order known {
d /.name_order get { /d 1 index get /p exec } 2 instantiate
}{
d /p load
} ifelse
end forall
} func-begin def
<< /a 1 /b 2 /c 3 /d 4 /e 5 /f 6 /g 7 /h 8 /i 9 /j 10 /k 11 /l 12 /m 13 >> dup ===
dup { pop =only ( ) print } foreach / =
{ exch pop =only ( ) print } foreach pc
/f << /a /integertype /b /realtype >> {
a b add
} typed-func def
1 2.0 f = / =
2 f Error: stackunderflow
%1.0 2 f Error: typecheck
pq
Norah@laptop ~
$ gsnd -q struct.ps
<< /i 9 /b 2 /m 13 /f 6 /j 10 /c 3 /g 7 /k 11 /d 4 /h 8 /a 1 /.name_order [/a /b /c /d /e /f /g /h /i /j /k /l /m] /l 12 /e 5 >>
a b c d e f g h i j k l m
1 2 3 4 5 6 7 8 9 10 11 12 13
3.0
Error: /stackunderflow in f
Operand stack:
2 --dict:4/6(L)--
Execution stack:
%interp_exit .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- --nostringval-- false 1 %stopped_push 1983 1 3 %oparray_pop 1982 1 3 %oparray_pop 1966 1 3 %oparray_pop 1852 1 3 %oparray_pop --nostringval-- %errorexec_pop .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval--
Dictionary stack:
--dict:1201/1684(ro)(G)-- --dict:0/20(G)-- --dict:78/200(L)-- --dict:24/42(L)-- --dict:2/2(L)--
Current allocation mode is local
Current file position is 3453
GPL Ghostscript 9.19: Unrecoverable error, exit code 1
[toc] | [prev] | [next] | [standalone]
| From | luser droog <luser.droog@gmail.com> |
|---|---|
| Date | 2017-07-15 20:00 -0700 |
| Subject | Re: Type-checked function arguments - Re: Binding - Re: Control Structures (Ordered Dictionaries) |
| Message-ID | <daa9484d-0b2d-4c25-ab85-2e9ec6ad22ee@googlegroups.com> |
| In reply to | #3138 |
On Monday, July 10, 2017 at 11:34:01 AM UTC-5, luser droog wrote:
> On Sunday, July 9, 2017 at 12:44:25 PM UTC-5, luser droog wrote:
> > On Tuesday, July 4, 2017 at 10:21:53 AM UTC-5, Carlos wrote:
> > > [luser droog <luser.droog@gmail.com>, 2017-07-03 20:01]
> > > > I tried using /fortuple, and as I read the commented-out line, it still
> > > > seems to me that it should work. But it trips an /invalidaccess error in
> > > > 'def' and the stack has some weird things like /IdiomRecognition on it.
> > >
> > > "Instantiate" first replaces the placeholders and then binds. Maybe it's
> > > related. In any case, it should be the other way around; if "a" happens to be
> > > executable, you don't want bind trying to do idiom replacements on it (and
> > > probably neither on "p").
> > > --
> >
> > Thanks. I'll leave 'bind' alone until I can read up on it. I'm not up-to-date
> > on the level 2 (and beyond) aspects.
> >
> > With the ordered dictionaries, we now have a nice syntax and mechanism for
> > declaring arguments with types. So here's the draft. New stuff at the bottom.
> >
>
> Reorganized. And added checking for stackunderflow.
>
Trying to do something with it, I rewrote my parser combinator stuff. A whole
bunch of problems just disappeared. I had to be careful in adding a second
wrapper around >>, but it appears to work. Now that dictionaries can capture
the order of keys, there's no need to sort. No /bubble. No comparing.
The cost in complication is when I want to be tricky with /inverse.
It generates the parser descriptor for the alternation of all character
codes 0-127. Then it runs through dictionary to be subtracted and
'undef's everything /except/ .name_order. Then it has to regenerate
/.name_order by popping anything no longer defined.
A few changes to the struct.ps stuff to accomodate all this. I forget the
details. I needed a /deep-map that digs into nested arrays. /instantiate
is obviously a mess, but there's no obvious better solution that I can see
but to offer many choices and leave it as a funky tool.
$ cat struct.ps
%!
% func - a syntax and mechanism for named, type-checked arguments and local variables in
% function definitions.
<<
/func { 1 index type /dicttype eq { typed-func }{ simple-func } ifelse }
/simple-func { func-begin { end } compose }
/typed-func { typed-func-begin { end } compose }
/func-begin { exch reverse /args-begin load curry exch compose }
/args-begin { dup length dict begin { exch def } forall }
/typed-func-begin { % name << /arg /typename >> { body }
exch dup /.name_order 2 copy get reverse put % name body args@
dup /.func_name 4 index put
{ dup /.name_order get length dict begin
dup /.func_name get /.func_name exch def
check-args } curry % name body {args@ ...begin... check-args}
exch % name {args->bool} body
{ /.func_name load cvx /.error_name load end signalerror } exch /ifelse cvx 3 array astore cvx
compose % name {args...body}
}
(>>){ % save an array of original keys in order as /.name_order
counttomark dup 1 add copy array astore exch pop
mark exch 2 { aload pop pop } fortuple ] /.name_order exch
counttomark dup dict begin 2 idiv { def } repeat pop currentdict end
}
/check-args { % ... <<args@>> -> bool True means fail
count 1 sub 1 index /.name_order get length lt {
/.error_name /stackunderflow cvx def pop true
}{
{ { check-arg } foreach } stopped
} ifelse
}
/check-arg { % ? name typename
2 index type eq { exch def }{ /.error_name /typecheck cvx def stop } ifelse
}
/instantiate {
{
{ { load } map } % Rule 0: load everything
{ { dup xcheck { load } if } map } % 1: load executables
{ { currentdict 1 index known { load } if } map } % 2: load current defs
{ { dup type/nametype eq { load } if } map } % 3: load names
{ { dup type/nametype eq 1 index xcheck and { load } if } map } % 4: load executable names
{ { dup type/nametype eq 1 index xcheck not and { load } if } map } % 5: load literal names
{ { currentdict 1 index known { load
dup type /arraytype eq { {} forall } if } if } deep-map } % 6: expand arrays
} exch get exec }
/first { 0 get }
/rest { 1 1 index length 1 sub getinterval }
/deep-map { 1 index type /arraytype ne { exec }{
1 index xcheck 3 1 roll
[ 3 1 roll /deep-map cvx 2 array astore cvx forall ] exch {cvx} if } ifelse }
/map { 1 index xcheck 3 1 roll [ 3 1 roll forall ] exch { cvx } if }
/each-map { 1 index xcheck 3 1 roll [ 3 1 roll foreach ] exch { cvx } if }
/reduce { exch dup first exch rest 3 -1 roll forall }
/curry { [ 3 1 roll {}forall ] cvx }
/compose { 2 array astore cvx { {}forall } map }
/reverse { dup xcheck exch [ exch reverse-rest ] exch { cvx } if }
/reverse-rest { dup length 0 eq { pop }{
dup rest /reverse-rest cvx 3 -1 roll /first cvx
4 array astore cvx exec
} ifelse }
/ps{pstack/ =}/pq{ps quit}/pc{ps clear}
>> begin
%errordict/typecheck{pstack} 3 copy pop get compose put
/fortuple {a n p}{
0 n /a load length 1 sub
{ /a exch /n getinterval /p exec } 2 instantiate
end for
} func-begin def
/foreach {d p}{
d type /dicttype eq {
d /.name_order known {
d /.name_order get { /d 1 index get /p exec } 2 instantiate
}{
d /p load
} ifelse
}{
d /p load
} ifelse
end forall
} func-begin def
currentfile flushfile
% Tests/Examples
<< /a 1 /b 2 /c 3 /d 4 /e 5 /f 6 /g 7 /h 8 /i 9 /j 10 /k 11 /l 12 /m 13 >> dup ===
dup { pop =only ( ) print } foreach / =
{ exch pop =only ( ) print } foreach pc
/f [/a /b] {
a b add
} func def
1 2 f = / =
/f {a b}{
a b add
} func def
1 2 f = / =
%2 f %Error: stackunderflow in exch
/f << /a /integertype /b /realtype >> {
a b add
} func def
1 2.0 f = / =
2 f %Error: stackunderflow
%1.0 2 f %Error: typecheck
pq
Norah@laptop ~
$ cat pc6.ps
(struct.ps) run <<
/# {length}
/| {array astore}
/x {2 index}
/y {1 index}
/z {dup}
/zy {exch}
/zxy {3 1 roll}
/yzx {3 2 roll}
/head {0 zy getinterval}
/tail {y # y sub getinterval}
/max {y y lt{zy}if pop}
/is {y type eq}
/pass { # 1 | zy pop }
/fail { pop pop [] }
/failed { z # 0 eq }
/passed { failed not }
/next { y tail }
/sum-up { zy {add} curry cvx forall }
/test { y # y # lt { fail }{ % input seek
y y # head y eq { pass }{ fail } ifelse } ifelse }
% /term { /test cvx 2 | cvx }
/term {str}{ {/str test} 6 instantiate } func
/alt2 {p q}{ {z /p zy /q compose} 6 instantiate } func
/seq2 {p q}{ {z /p zy {next /q sum-up} curry cvx map} 6 instantiate } func
/some { z many seq2 }
/maybe { {()pass} alt2 }
/many { {{{}exec}exec} {} deep-map
%[[[]cvx/exec cvx]cvx/exec cvx]cvx
z 0 get zxy seq2 maybe
y y 0 zy put zy pop }
/parser { /stringtype is { term }{
/arraytype is { sequence }{
/dicttype is { alternate }{ } ifelse } ifelse } ifelse }
/sequence { z xcheck { }{ {parser} map {seq2} reduce } ifelse }
/alternate { {} each-map {fix-up} map {parser} map {alt2} reduce }
/fix-up { /nulltype is { pop }{
/nametype is { z # string cvs }{ } ifelse } ifelse }
/* { parser many }
/+ { parser some }
/? { parser maybe }
/char-class { << zy 1 {} fortuple >> }
(>>) [ {counttomark 2 mod 1 eq {null} if} {}forall
(>>) load dup type /arraytype eq {/exec cvx} if ] cvx
/inverse {
<< 0 1 127 { 1 string z 0 4 -1 roll put null } for >>
exch {} each-map {fix-up} map << exch {null} forall >>
minus-keys
}
/minus-keys {
y zxy { pop dup /.name_order ne {undef}{pop pop} ifelse z } forall pop
dup /.name_order 2 copy get [ exch {
counttomark 2 add index 1 index known not { pop } if
} forall ] put
}
>> begin
(xxxy) [(xxx) (y)] parser exec pc
(xxxy) [ (x) * (xy) ] parser exec pc
(x) (abcde) char-class inverse parser dup == exec pc
pq
Norah@laptop ~
$ gsnd -dNOSAFER pc6.ps
GPL Ghostscript 9.19 (2016-03-23)
Copyright (C) 2016 Artifex Software, Inc. All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
[4]
{4}
{z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z z (\000) test zy (\001) test compose zy (\002) test compose zy (\003) test compose zy (\004) test compose zy (\005) test compose zy (\006) test compose zy (\007) test compose zy (\b) test compose zy (\t) test compose zy (\n) test compose zy (\013) test compose zy (\f) test compose zy (\r) test compose zy (\016) test compose zy (\017) test compose zy (\020) test compose zy (\021) test compose zy (\022) test compose zy (\023) test compose zy (\024) test compose zy (\025) test compose zy (\026) test compose zy (\027) test compose zy (\030) test compose zy (\031) test compose zy (\032) test compose zy (\033) test compose zy (\034) test compose zy (\035) test compose zy (\036) test compose zy (\037) test compose zy ( ) test compose zy (!) test compose zy (") test compose zy (#) test compose zy ($) test compose zy (%) test compose zy (&) test compose zy (') test compose zy (\() test compose zy (\)) test compose zy (*) test compose zy (+) test compose zy (,) test compose zy (-) test compose zy (.) test compose zy (/) test compose zy (0) test compose zy (1) test compose zy (2) test compose zy (3) test compose zy (4) test compose zy (5) test compose zy (6) test compose zy (7) test compose zy (8) test compose zy (9) test compose zy (:) test compose zy (;) test compose zy (<) test compose zy (=) test compose zy (>) test compose zy (?) test compose zy (@) test compose zy (A) test compose zy (B) test compose zy (C) test compose zy (D) test compose zy (E) test compose zy (F) test compose zy (G) test compose zy (H) test compose zy (I) test compose zy (J) test compose zy (K) test compose zy (L) test compose zy (M) test compose zy (N) test compose zy (O) test compose zy (P) test compose zy (Q) test compose zy (R) test compose zy (S) test compose zy (T) test compose zy (U) test compose zy (V) test compose zy (W) test compose zy (X) test compose zy (Y) test compose zy (Z) test compose zy ([) test compose zy (\\) test compose zy (]) test compose zy (^) test compose zy (_) test compose zy (`) test compose zy (f) test compose zy (g) test compose zy (h) test compose zy (i) test compose zy (j) test compose zy (k) test compose zy (l) test compose zy (m) test compose zy (n) test compose zy (o) test compose zy (p) test compose zy (q) test compose zy (r) test compose zy (s) test compose zy (t) test compose zy (u) test compose zy (v) test compose zy (w) test compose zy (x) test compose zy (y) test compose zy (z) test compose zy ({) test compose zy (|) test compose zy (}) test compose zy (~) test compose zy (\177) test compose}
{1}
[toc] | [prev] | [next] | [standalone]
| From | luser droog <luser.droog@gmail.com> |
|---|---|
| Date | 2017-07-25 23:07 -0700 |
| Subject | Re: Type-checked function arguments - Re: Binding - Re: Control Structures (Ordered Dictionaries) |
| Message-ID | <34e28872-ee7e-430e-a016-00146b8e7739@googlegroups.com> |
| In reply to | #3141 |
I'm pretty happy with this bit of code. So this post is a recap and exploded
commentary. Hopefully skipping the first round of ("any comments?" "please explain").
Of course anyone with opinions, feel free to jump in.
We want to borrow ideas from functional programming like creating and
passing around functions. Postscript has most of the support we need
and already uses this style for its own control structures. Control structures
like 'if', 'for', 'loop', 'exec' all take a procedure as argument. The
procedure body passed to 'for' can be treated like a function which takes
at least one argument which is being supplied automatically.
So we want to write functions which receive procedures as arguments
and behave as transparently as possible from the point of view of
the argument procedure at the time when it gets called.
We want a nice syntax for defining functions with named arguments.
To make these definitions local to the function invocation -- and allow
recursion -- we want to wrap the procedure body with 'dict begin' and 'end'.
To make a new function that can receive and execute one of these
functions as an argument, it may need finer control of when the 'end'
gets executed. It may then use /typed-func-begin or /func-begin to
add the function preamble and then place the 'end' whereever is convenient
to get the dict out of the way before calling the argument function.
Does all that make sense? I feel like I'm rambling.
So here's some code.
$ cat struct.ps
%!
% func - a syntax and mechanism for named, type-checked arguments and local variables in
% function definitions.
<<
/func { 1 index type /dicttype eq { typed-func }{ simple-func } ifelse }
/simple-func { func-begin { end } compose }
/typed-func { typed-func-begin { end } compose }
So far so good? Either you're doing
/name {args} {body} func def
or
/name <</arg /type>> {body} func def
. So the single call to 'func' does both! Or either. or whatever.
Both do something with a 'begin' in it and then tack and 'end' on the
end. This should sound familiar from my lead-up above.
/func-begin { exch reverse /args-begin load curry exch compose }
/args-begin { dup length dict begin { exch def } forall }
func-begin reverses the list so it can loop against the stack top-down
yet we get to write the names left to right. This is all stuff we could
do by hand, eg. '/f { 3 dict begin /y exch def /x exch def ... end } def'
But we get to write '/f {x y}{ ... } func def', which is already nicer.
Now, if the argument list is a dictionary constructed with '>>', it can
contain pairs of (/argument-name /typename) and as a plus, we can hijack
the '>>' name and also grab all the keys in their original order in an
array. So assuming the dictionary has been supplemented with this definition
of an array of keys as /.name_order, we reverse this list just as we do
for the simple function. Then we add code to 'begin' the local dictionary
and check the types of the arguments while defining them. If the type checking
fails, then issue the proper /typecheck error, otherwise execute the function
body.
/typed-func-begin { % name << /arg /typename >> { body }
exch dup /.name_order 2 copy get reverse put % name body args@
dup /.func_name 4 index put
{ dup /.name_order get length dict begin
dup /.func_name get /.func_name exch def
check-args } curry % name body {args@ ...begin... check-args}
exch % name {args->bool} body
{ /.func_name load cvx /.error_name load end signalerror } exch /ifelse cvx 3 array astore cvx
compose % name {args...body}
}
This next function does the new version of '>>' which grabs the keys. It just
reimplements the basic behavior. But it could also be written to re-use the
existing operator if desired.
(>>){ % save an array of original keys in order as /.name_order
counttomark dup 1 add copy array astore exch pop
mark exch 2 { aload pop pop } fortuple ] /.name_order exch
counttomark dup dict begin 2 idiv { def } repeat pop currentdict end
}
And the boilerplate of checking each argument for type. If there are not
enough arguments present on the stack, then the error to be returned is
/stackunderflow instead of /typecheck.
/check-args { % ... <<args@>> -> bool True means fail
count 1 sub 1 index /.name_order get length lt {
/.error_name /stackunderflow cvx def pop true
}{
{ { check-arg } foreach } stopped
} ifelse
}
/check-arg { % ? name typename
2 index type eq { exch def }{ /.error_name /typecheck cvx def stop } ifelse
}
Next is /instantiate which is used to produce procedure bodies with parts
that are mapped through a dictionary. The local dictionary that we've been
'begin'ing everywhere serves nicely for this later when it's used. There are
at least 6 different rules that might be chosen to determine which parts in
template will be substituted.
/instantiate {
{
{ { load } map } % Rule 0: load everything
{ { dup xcheck { load } if } map } % 1: load executables
{ { currentdict 1 index known { load } if } map } % 2: load current defs
{ { dup type/nametype eq { load } if } map } % 3: load names
{ { dup type/nametype eq 1 index xcheck and { load } if } map } % 4: load executable names
{ { dup type/nametype eq 1 index xcheck not and { load } if } map } % 5: load literal names
{ { currentdict 1 index known { load
dup type /arraytype eq { {} forall } if } if } deep-map } % 6: expand arrays
} exch get exec }
The big pieces covered, we need some smaller support functions for the
above chicanery.
/first { 0 get }
/rest { 1 1 index length 1 sub getinterval }
Deep-map is used to map a nested array structure, recursively mapping
all sub-arrays as it goes.
/deep-map { 1 index type /arraytype ne { exec }{
1 index xcheck 3 1 roll
[ 3 1 roll /deep-map cvx 2 array astore cvx forall ] exch {cvx} if } ifelse }
Map and reduce. Curry and Compose. Reversing the list.
/map { 1 index xcheck 3 1 roll [ 3 1 roll forall ] exch { cvx } if }
/each-map { 1 index xcheck 3 1 roll [ 3 1 roll foreach ] exch { cvx } if }
/reduce { exch dup first exch rest 3 -1 roll forall }
/curry { [ 3 1 roll {}forall ] cvx }
/compose { 2 array astore cvx { {}forall } map }
/reverse { dup xcheck exch [ exch reverse-rest ] exch { cvx } if }
/reverse-rest { dup length 0 eq { pop }{
dup rest /reverse-rest cvx 3 -1 roll /first cvx
4 array astore cvx exec
} ifelse }
/ps{pstack/ =}/pq{ps quit}/pc{ps clear}
>> begin
%errordict/typecheck{pstack} 3 copy pop get compose put
And a few more functions used in the typed-func-begin code
above, but created using just the preamble of the simple-func
as described in the lead-in. Then it 'end's before passing
the argument procedure to over to a built-in control operator.
/fortuple {a n p}{
0 n /a load length 1 sub
{ /a exch /n getinterval /p exec } 2 instantiate
end for
} func-begin def
/foreach {d p}{
d type /dicttype eq {
d /.name_order known {
d /.name_order get { /d 1 index get /p exec } 2 instantiate
}{
d /p load
} ifelse
}{
d /p load
} ifelse
end forall
} func-begin def
And that's all you need for locally defined, named arguments to functions.
And easy declaration of types for functions which check the types of
their arguments.
It may be desirable to add some addition "pattern" pseudo-typenames
so you can check some of the arguments and use /any or /anytype to
ignore the types of other arguments.
Now ... questions?
[toc] | [prev] | [next] | [standalone]
| From | Carlos <carlos@cvkm.cz> |
|---|---|
| Date | 2017-07-26 16:41 +0200 |
| Subject | Re: Type-checked function arguments - Re: Binding - Re: Control Structures (Ordered Dictionaries) |
| Message-ID | <20170726164132.25b1eb9a@samara.DOMA> |
| In reply to | #3143 |
[luser droog <luser.droog@gmail.com>, 2017-07-25 23:07]
[...]
> Of course anyone with opinions, feel free to jump in.
I have, for now, having superficially perused the post, one superficial
opinion: I hate the redefinition of ">>". I'd reuse {}, making executable names
parameter names and literal names type names. That'd also allow having typed
and untyped parameters in the same function. Of course it'd also make
parameter parsing more complex.
Some examples to get a feel...
/arrayput { arr/arraytype idx/integertype item } { ... } func def
/fortuple { arr size/integertype proc/arraytype } { ... } func-begin def
[...]
> It may be desirable to add some addition "pattern" pseudo-typenames
> so you can check some of the arguments and use /any or /anytype to
> ignore the types of other arguments.
Or that's another possibility for untyped parameters, of course. I have some
unfinished multimethods (not exactly what you're doing but related) POC I'll
share in the evening, maybe there can be some cross-pollination of ideas :).
--
[toc] | [prev] | [next] | [standalone]
| From | luser droog <luser.droog@gmail.com> |
|---|---|
| Date | 2017-07-26 10:29 -0700 |
| Subject | Re: Type-checked function arguments - Re: Binding - Re: Control Structures (Ordered Dictionaries) |
| Message-ID | <fefb7a22-7098-4217-a181-b69995828efb@googlegroups.com> |
| In reply to | #3144 |
On Wednesday, July 26, 2017 at 9:41:33 AM UTC-5, Carlos wrote:
> [luser droog <luser.droog@gmail.com>, 2017-07-25 23:07]
> [...]
> > Of course anyone with opinions, feel free to jump in.
>
> I have, for now, having superficially perused the post, one superficial
> opinion: I hate the redefinition of ">>". I'd reuse {}, making executable names
> parameter names and literal names type names. That'd also allow having typed
> and untyped parameters in the same function. Of course it'd also make
> parameter parsing more complex.
>
> Some examples to get a feel...
>
> /arrayput { arr/arraytype idx/integertype item } { ... } func def
>
> /fortuple { arr size/integertype proc/arraytype } { ... } func-begin def
Interesting. It looks nice with the slashes as separators.
Might be arduous to implement. I'm not directly seeing an easy way to
transform it into the structures I have.
> [...]
> > It may be desirable to add some addition "pattern" pseudo-typenames
> > so you can check some of the arguments and use /any or /anytype to
> > ignore the types of other arguments.
>
> Or that's another possibility for untyped parameters, of course. I have some
> unfinished multimethods (not exactly what you're doing but related) POC I'll
> share in the evening, maybe there can be some cross-pollination of ideas :).
>
Very cool. I used extended types like this in xpost to pattern-match the stack
and call the correct C function. There's /anytype which of course matches
any object type, /numbertype which matches /integertype or /realtype,
/floattype which matches /realtype and automatically promotes /integertype
to /realtype, and /proctype which matches /arraytype with the executable flag.
I think the /numbertype could be very useful. Maybe /proctype too.
/floattype is nice at the C level, but probably unnecessary. And it may
be simpler to exclude it.
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.postscript
csiph-web