Path: csiph.com!eternal-september.org!feeder.eternal-september.org!news.eternal-september.org!.POSTED!not-for-mail From: Carlos Newsgroups: comp.lang.postscript Subject: Re: Control Structures Date: Fri, 23 Jun 2017 00:28:01 +0200 Organization: A noiseless patient Spider Lines: 139 Message-ID: <20170623002801.61560cd4@samara.DOMA> References: <2f65c3d3-60f0-48d6-af25-7af2964b9254@googlegroups.com> Mime-Version: 1.0 Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Injection-Info: mx02.eternal-september.org; posting-host="0af5978e958d4394258cb27fa0f2b357"; logging-data="13842"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1+bExicYBZLE1KKoVUWKuV312x/K9Jy4cY=" X-Newsreader: Claws Mail 3.14.1 (GTK+ 2.24.31; x86_64-pc-linux-gnu) Cancel-Lock: sha1:Y5JyGDx1gEk7WXd4cy1Cn9BR8OE= Xref: csiph.com comp.lang.postscript:3125 [luser droog , 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 ()=