• sprintf

    From luser droog@21:1/5 to All on Mon Mar 13 17:05:48 2023
    The variable arguments are interpolated in reverse order (ie. the
    normal top-of-stack down-ward way). I'm not sure how useful it
    actually is. Should it take an array for the variable arguments and
    take them left-to-right?


    /sprintf { % ... (format) sprintf -
    [ exch
    0 { % ... x? [ ... format i
    2 copy exch length ge { pop pop exit } if
    2 copy 1 getinterval % ... x? [ ... f i f_i
    dup (%) eq {
    pop % ... x? [ ... f i
    counttomark 2 add -1 roll % ... [ ... f i x
    256 string cvs % ... [ ... f i (x)
    } if
    3 1 roll
    1 add
    } loop
    ] dup 0 exch {length add} forall string exch % (dest) [(p)(a)(r)(t)(s)]
    0 exch { % d pos (part)
    3 copy putinterval
    length add
    } forall
    pop
    } def

    /a 25 def
    /b /B def

    b a (format % %) sprintf
    pstack % => (format 25 B)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Newall@21:1/5 to luser droog on Wed Mar 15 18:46:10 2023
    On 14/3/23 11:05, luser droog wrote:
    The variable arguments are interpolated in reverse order (ie. the
    normal top-of-stack down-ward way). I'm not sure how useful it
    actually is. Should it take an array for the variable arguments and
    take them left-to-right?

    I love this game! :-)

    Here's a slightly different implementation that uses search instead of
    testing each character.

    /sprintf {
    % construct array of strings
    [ exch (%) {
    search not { exit } if
    counttomark 2 add -1 roll 256 string cvs 4 2 roll
    } loop ]
    % merge strings into a single array
    0 1 index { length add } forall string exch
    0 exch { 3 copy putinterval length add } forall
    pop
    } def

    It's interesting that we mostly came up with identical code.

    For fun, this one prints arrays, dictionaries and other stuff (instead
    of --nostringval--):

    /_sprintf {
    dup type {
    dup/arraytype eq 1 index/packedarraytype eq or {
    pop ([)
    exch { _sprintf ( ) } forall
    dup ( ) eq {pop} if
    (]) exit
    } if
    dup/dicttype eq {
    pop (<<)
    exch {
    mark 3 -1 roll _sprintf ( => )
    counttomark 2 add -2 roll pop
    _sprintf (, )
    } forall
    dup (, ) eq {pop} if
    (>>) exit
    } if
    dup/stringtype eq { pop exit } if
    dup/nametype eq { pop (/) exch dup length string cvs exit } if
    dup/operatortype eq 1 index/integertype eq or 1 index/realtype eq or
    { pop 20 string cvs exit }
    if
    dup/booleantype eq { pop {(true)}{(false)} ifelse exit } if
    dup/marktype eq { pop (MARK) exit } if
    dup/nulltype eq { pop (-) exit } if
    dup/savetype eq { pop (-save-) exit } if
    dup/filetype eq { pop (-file-) exit } if
    dup/fonttype eq { pop (-font-) exit } if
    dup/gstatetype eq { pop (-gstate-) exit } if
    /sprintf_element cvx errordict/typecheck get exec
    } loop
    } def

    /sprintf {
    % construct array of sub-strings plus values to format
    [ exch (%) {
    search not { exit } if
    counttomark 2 add -1 roll 4 2 roll
    } loop ]
    % turn it into an array of strings
    [exch{_sprintf}forall]
    % merge strings into a single array
    0 1 index { length add } forall string exch
    0 exch { 3 copy putinterval length add } forall
    pop
    } def

    Bonus points if you enclose executable arrays and in braces.

    Regards,

    David

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to David Newall on Thu Mar 23 08:54:49 2023
    On Wednesday, March 15, 2023 at 2:46:26 AM UTC-5, David Newall wrote:
    On 14/3/23 11:05, luser droog wrote:
    The variable arguments are interpolated in reverse order (ie. the
    normal top-of-stack down-ward way). I'm not sure how useful it
    actually is. Should it take an array for the variable arguments and
    take them left-to-right?
    I love this game! :-)

    Here's a slightly different implementation that uses search instead of testing each character.

    Very cool. That ought to run faster and use less memory.

    [snip]
    dup/stringtype eq { pop exit } if
    dup/nametype eq { pop (/) exch dup length string cvs exit } if dup/operatortype eq 1 index/integertype eq or 1 index/realtype eq or
    { pop 20 string cvs exit }
    if
    dup/booleantype eq { pop {(true)}{(false)} ifelse exit } if
    dup/marktype eq { pop (MARK) exit } if
    dup/nulltype eq { pop (-) exit } if
    dup/savetype eq { pop (-save-) exit } if
    dup/filetype eq { pop (-file-) exit } if
    dup/fonttype eq { pop (-font-) exit } if
    dup/gstatetype eq { pop (-gstate-) exit } if
    /sprintf_element cvx errordict/typecheck get exec

    I'd be tempted to pull these out into a separate dictionary.

    /_sprintf_dict <<
    /stringtype{ pop exit }
    /nametype{ pop (/) exch dup length string cvs exit }
    ...
    /default{ pop (-unsupported-type-) exit }
    def

    {
    ...
    //_sprintf_dict 1 index type 2 copy known not {pop/default} if get exec
    ...
    }


    Another bonus is that you could implement `==` quite easily if a `sprintf`
    like this were available.

    /== { (%) sprintf print } def

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to luser droog on Thu Mar 23 21:16:00 2023
    On Thursday, March 23, 2023 at 10:54:51 AM UTC-5, luser droog wrote:
    On Wednesday, March 15, 2023 at 2:46:26 AM UTC-5, David Newall wrote:
    On 14/3/23 11:05, luser droog wrote:
    The variable arguments are interpolated in reverse order (ie. the
    normal top-of-stack down-ward way). I'm not sure how useful it
    actually is. Should it take an array for the variable arguments and
    take them left-to-right?
    I love this game! :-)

    Here's a slightly different implementation that uses search instead of testing each character.
    Very cool. That ought to run faster and use less memory.


    A little further inspiration. I first wanted to bust it up into smaller re-usable
    functions. This has the disadvantage of creating and discarding more intermediate arrays, but oh well. Then I re-tooled it to use search instead
    of going char by char, but now it uses more intermediate data.

    /sprintf2 {
    format
    join
    } def

    /format { % ... obj (format%string) format [(f)(o)(r)(m)(a)(t)(obj*)(s)(t)(r)(i)(n)(g)]
    [ exch
    {
    dup (%) eq { pop snag convert } if
    } foreach
    ]
    } def

    /foreach { % array|string proc foreach - % array[1]|string[1] proc ?
    2 dict begin {proc src}{exch def}forall
    0 1 /src load length 1 sub ({ % i
    //src exch 1 getinterval
    //proc exec
    }) cvx exec end for
    } def

    /snag {
    counttomark 2 add -1 roll
    } def

    /join { % [(a){b)(c)] join (abc)
    0 1 index {
    length add
    } forall % src dst-length
    1 index 0 get type /stringtype eq {string}{array} ifelse
    exch % dst src
    0 exch {
    3 copy putinterval
    length add
    } forall
    pop
    } def


    /convert /convert2 cvx def

    /convert1 {
    256 string cvs
    } def

    /convert-dict <<
    /stringtype { }
    /arraytype { ([ ) exch { convert ( ) } forall (]) }
    /default { 256 string cvs }
    def
    /convert2 {
    //convert-dict 1 index type 2 copy known not { pop /default } if get exec
    } def


    /sprintf3 {
    (%) split
    [ exch
    { dup (%) eq { pop snag convert } if } forall
    ]
    join
    } def

    /split { % (s.t.r.i.n.g) (.) split [(s)(.)(t)(.)(r)(.)(i)(.)(n)(.)(g)]
    [ 3 1 roll
    {
    search not { exit } if % [ ,,, (t.r.i.n.g) (.) (s)
    3 1 roll dup 3 1 roll % [ ... (s) (.) (t.r.i.n.g) (.)
    } loop
    ]
    } def


    /a 25 def
    /b /B def

    b a (format % %) sprintf
    pstack % => (format 25 B)
    clear

    b a (format % %) sprintf2
    pstack % => (format 25 B)
    clear

    [1 2 3] b a (format % % %) sprintf3
    pstack % => (format 25 B [ 1 2 3 ])

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to luser droog on Sun Mar 26 19:19:26 2023
    On Thursday, March 23, 2023 at 11:16:01 PM UTC-5, luser droog wrote:
    On Thursday, March 23, 2023 at 10:54:51 AM UTC-5, luser droog wrote:
    On Wednesday, March 15, 2023 at 2:46:26 AM UTC-5, David Newall wrote:
    On 14/3/23 11:05, luser droog wrote:
    The variable arguments are interpolated in reverse order (ie. the normal top-of-stack down-ward way). I'm not sure how useful it actually is. Should it take an array for the variable arguments and take them left-to-right?
    I love this game! :-)

    Here's a slightly different implementation that uses search instead of testing each character.
    Very cool. That ought to run faster and use less memory.

    A little further inspiration. I first wanted to bust it up into smaller re-usable
    functions. This has the disadvantage of creating and discarding more intermediate arrays, but oh well. Then I re-tooled it to use search instead of going char by char, but now it uses more intermediate data.


    Got rid of the intermediate arrays at the cost of parsing and constructing
    a new procedure body in the new, weird looping proc. This code uses /convert
    as defined in my previous message. [I'm posting through GG until I fix my gnus setup for posting, so I can't quote indented code without losing indentation
    or doing extra work to add it back.]

    /sprintf4 { % data (format%rem) sprintf4 (format<data>*rem)
    [ exch
    (%) { % data [ (format) (rem) (%) [
    pop snag % [ (format) (rem) (%) data
    mark exch % [ (format) (rem) (%) [ data
    convert % [ (format) (rem) (%) (<data>*)
    } on-matches
    join-to-mark
    } def

    /on-matches { % string seek proc
    1 dict begin /proc exch def
    ({
    search not { exit } if
    3 1 roll mark % pre post match [
    //proc exec % pre post match [ proc*
    counttomark % pre post match [ proc* n
    dup 2 add -1 roll pop % pre post match proc* n
    dup 2 add exch % pre post match proc* n+2 n
    roll % pre proc* post match
    }) cvx exec end loop
    } def

    /join-to-mark { % [ <obj1> .. <objN>
    counttomark dup 1 add copy % [ <obj1> .. <objN> n <obj1> .. <objN> n
    0 exch { % ... <obj1> .. <objn> 0
    exch length add
    } repeat % [ <obj1> .. <objN> n length
    counttomark 1 sub index type /stringtype eq
    {string}{array} ifelse % [ <obj1> .. <objN> n dest
    exch 0 exch { % [ <obj1> .. <objN> dest pos
    counttomark -1 roll % [ <obj2> .. <objN> dest pos <obj1>
    3 copy putinterval length add % [ <obj2> .. <objN> dest' pos'
    } repeat % [ dest length
    pop exch pop
    } def

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Newall@21:1/5 to luser droog on Mon Mar 27 19:05:09 2023
    On 24/3/23 02:54, luser droog wrote:
    I'd be tempted to pull these out into a separate dictionary.

    /_sprintf_dict <<
    /stringtype{ pop exit }
    /nametype{ pop (/) exch dup length string cvs exit }
    ...
    /default{ pop (-unsupported-type-) exit }
    def
    {
    ...
    //_sprintf_dict 1 index type 2 copy known not {pop/default} if get
    exec
    ...
    }

    Very smart.

    Here's a tiny improvement to split:

    /split { % (s.t.r.i.n.g) (.) split [(s)(.)(t)(.)(r)(.)(i)(.)(n)(.)(g)]
    [ 3 1 roll
    {
    search {1 index 4 2 roll} {exit} ifelse
    } loop
    ]
    } def

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)