An enhanced syntax for defining functions in PostScript

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
2
down vote

favorite












This file enhances PostScript with a few new syntactic niceties for defining functions with named arguments and even type-checking.



block



The first part of the enhancement consists of a new control structure called a "block". A block is a list of pairs which will be collected as key/value pairs into a dictionary and then the special key main gets called. This much allows us to elide the 'def' for all functions and data, and also the / decorations on all the function names.




main f g h
f
g
h
block


This code defines a main function and 3 functions that main calls. When block executes, all 4 functions are defined in a dictionary and then main gets called.



Importantly, it lets you put main at the top to aid top-down coding.



func



Another enhancement is the func syntax. A function can be created to accept named arguments by calling func with the array of argument names and the array of the body of the function. To do this within the block construct where normally the contents of the block are not executed but just collected, you can force execution by prefixing the @ sign to func. Any name can be executed "at compile time" with this @ prefix, but only at the top-level.



Using func you can give names to the arguments of functions by enclosing them in curly braces before the function body:




main
3 f
4 5 g

f x 1 x div sin @func
g x y x log y log mul @func
block


This code creates a function f of one argument x, and a function g of two arguments x and y. The function body is augmented with code which takes these 2 objects from the stack and defines then in a new local dictionary created and begined for this invocation of the function coupled with an end at the end.



For some use-cases like implementing control structures, it is useful to place the end more strategically. The fortuple function illustrates this by using @func-begin which does not add end at the end. It then places the end earlier, before calling back to the p argument.



A function can also declare the types that its arguments must have by enclosing them in parentheses and using executable names for the argument names and literal names for the types:




main
3 4 p

p (x/integer y/integer) x y add @func
block


This augments the function body with code which checks that there are indeed enough objects on the stack, or else it triggers a stackunderflow error. Then it checks that all of the arguments have the expected type, or else it triggers a typecheck error. The types here are written without the letters type at the end; these are added automatically.



You can omit the type name with the parenthesized syntax and it will allow any type for that argument. If you omit all the type names you still get the stackunderflow checking. With any of these errors the name of the user function is reported in the error message for easier debugging.



Implementation



Implementation-wise, the foundation is the pairs construct which is an array which gets traversed with forall. Any name beginning with @ gets the @ stripped off and the remainder gets executed. The results are enclosed in << and >> to create a dictionary.



The first dictionary defines everything to do with pairs including pairs-def which adds the key/value pairs into the current dictionary rather than begin a new dictionary. The next two sections add their functions to this same dictionary.



This justifies (somewhat) the cuddled style of bracing. The whole implementation is split into 3 layers but the result is only 1 dictionary on the dictstack with all of these functions in it.



The middle section defines block and func and all of the functionality for the simple-func style of defining functions. The third section implements two looping control structures which use the simple-func style. These looping functions are then used by the more complex typed-func style.



Many of the functions used to implement all of this are useful in their own right so they are also supplied to the user, like curry compose reduce.



There is some simplistic testing code at the bottom guarded by /debug where. So if this file is simply run from another file, it will skip the testing code. But if the key debug is defined somewhere on the dictstack, then the testing code will execute. So with ghostscript, testing can be invoked with gs -ddebug struct2.ps.



The testing code itself illustrates the overhead of the code added by func. For type checking, it adds a fair amount of code.



%!
% struct2.ps An enhanced PostScript syntax for defining functions with named,
% type-checked arguments. Using @func within a block or other construct that uses
% 'pairs' accomplishes a sort of compile-time macro expansion of the shorthand function description.
<<
/pairs-begin pairs begin
/pairs-def pairs def forall
/pairs << exch explode >>
/explode @exec forall
/@exec dup type /nametype eq exec-if-@ if
/exec-if-@ dup dup length string cvs dup first (@) first eq exec@ pop ifelse
/first 0 get /exec@ exch pop rest cvn cvx exec
/rest 1 1 index length 1 sub getinterval
>> begin
block pairs-begin main end
func 1 index type /stringtype eq typed-func simple-func ifelse
simple-func func-begin end compose
typed-func exch args-and-types reverse make-type-name map check-stack 3 1 roll
exch simple-func compose
func-begin exch reverse /args-begin load curry exch compose
args-begin dup length dict begin exch def forall
args-and-types /was_x false def [ exch each-specifier fortokens fix-last ] dup args exch types
each-specifier dup xcheck /is_x exch def is_x was_x and null exch if /was_x is_x def
fix-last counttomark 2 mod 1 eq null if
check-stack pop 4 index cvlit cvx /stackunderflow signalerror curry compose
/if cvx 2 array astore cvx check-count exch compose curry
3 index cvlit cvx /typecheck signalerror curry
/if cvx 2 array astore cvx check-types exch compose compose
check-count dup length count 2 sub gt
check-types dup length 1 add copy true exch check-type and forall exch pop not
check-type dup null eq 3 -1 roll pop pop true 3 -1 roll type eq ifelse
make-type-name dup type /nametype eq dup length 4 add string dup dup 4 2 roll cvs
2 copy 0 exch putinterval length (type) putinterval cvn if
args [ exch 2 0 get fortuple ]
types [ exch 2 1 get fortuple ]
map 1 index xcheck 3 1 roll [ 3 1 roll forall ] exch cvx if
reduce exch dup first exch rest 3 -1 roll forall
rreduce exch aload length 1 sub dup 3 add -1 roll repeat
curry [ 3 1 roll forall ] cvx @pop
dup length 1 add array dup 0 5 -1 roll put dup 1 4 -1 roll putinterval cvx
compose 2 array astore cvx forall map @pop
1 index length 1 index length add array dup 0 4 index putinterval
dup 4 -1 roll length 4 -1 roll putinterval cvx
reverse [ exch dup length 1 sub -1 0 2 copy get 3 1 roll pop for pop ]
pairs-def
fortokens src proc src token exch /src exch storeexitifelse proc loop @func
fortuple a n p 0 n /a load length 1 sub
/a exch /n getinterval /p exec load-if-literal-name map end for
@func-begin
load-if-literal-name dup type /nametype eq 1 index xcheck not and load if
pairs-def

/debug wherepopcurrentfile flushfileifelse

- sub + add * mul %:= exch def += dup load 3 -1 roll + store

var 2 3 @add


f x y z x y z + * @func

f' x y z x y z + * end @func-begin

f'' z y xargs-begin x y z + * end


g(x/integer y/integer z/real) x y z + * @func

g'
[/realtype/integertype/integertype]
check-count pop /g cvx /stackunderflow signalerror if
check-types /g cvx /typecheck signalerror if

z y xargs-begin x y z + * end



h(x y z) x y z + * @func %@dup @==

h'
[null null null]
check-count pop /h cvx /stackunderflow signalerror if
check-types /h cvx /typecheck signalerror if

z y xargs-begin x y z + * end



main
var ==
[ 1 2 3 4 5 ] - rreduce ==
/ =
3 4 5 f ==
3 4 5 f' ==
3 4 5 f'' ==
/ =
3 4 5.0 g =
3 4 5.0 g' =
3 4 5 g = stopped $error /errorname get =only ( in ) print $error /command get = if
/ =
clear
3 4 h = stopped $error /errorname get =only ( in ) print $error /command get = if
clear
3 4 5 h =
3.0 4.0 5.0 h = stopped $error /errorname get =only ( in ) print $error /command get = if
3.0 4.0 5.0 h' = stopped $error /errorname get =only ( in ) print $error /command get = if
quit


block


The output from the testing code:



$ gsnd -q -ddebug struct2.ps
5
3

27
27
27

27.0
27.0
typecheck in g

stackunderflow in h
27
27.0
27.0


Are there improvements to make to the implementation or the behavior? Currently the simple-func style does not check that there are enough arguments but just tries to define them assuming that they're there. Would it be better to add this checking, or is it better to have this low-overhead version which does not add (possibly wasteful) checks?







share|improve this question

























    up vote
    2
    down vote

    favorite












    This file enhances PostScript with a few new syntactic niceties for defining functions with named arguments and even type-checking.



    block



    The first part of the enhancement consists of a new control structure called a "block". A block is a list of pairs which will be collected as key/value pairs into a dictionary and then the special key main gets called. This much allows us to elide the 'def' for all functions and data, and also the / decorations on all the function names.




    main f g h
    f
    g
    h
    block


    This code defines a main function and 3 functions that main calls. When block executes, all 4 functions are defined in a dictionary and then main gets called.



    Importantly, it lets you put main at the top to aid top-down coding.



    func



    Another enhancement is the func syntax. A function can be created to accept named arguments by calling func with the array of argument names and the array of the body of the function. To do this within the block construct where normally the contents of the block are not executed but just collected, you can force execution by prefixing the @ sign to func. Any name can be executed "at compile time" with this @ prefix, but only at the top-level.



    Using func you can give names to the arguments of functions by enclosing them in curly braces before the function body:




    main
    3 f
    4 5 g

    f x 1 x div sin @func
    g x y x log y log mul @func
    block


    This code creates a function f of one argument x, and a function g of two arguments x and y. The function body is augmented with code which takes these 2 objects from the stack and defines then in a new local dictionary created and begined for this invocation of the function coupled with an end at the end.



    For some use-cases like implementing control structures, it is useful to place the end more strategically. The fortuple function illustrates this by using @func-begin which does not add end at the end. It then places the end earlier, before calling back to the p argument.



    A function can also declare the types that its arguments must have by enclosing them in parentheses and using executable names for the argument names and literal names for the types:




    main
    3 4 p

    p (x/integer y/integer) x y add @func
    block


    This augments the function body with code which checks that there are indeed enough objects on the stack, or else it triggers a stackunderflow error. Then it checks that all of the arguments have the expected type, or else it triggers a typecheck error. The types here are written without the letters type at the end; these are added automatically.



    You can omit the type name with the parenthesized syntax and it will allow any type for that argument. If you omit all the type names you still get the stackunderflow checking. With any of these errors the name of the user function is reported in the error message for easier debugging.



    Implementation



    Implementation-wise, the foundation is the pairs construct which is an array which gets traversed with forall. Any name beginning with @ gets the @ stripped off and the remainder gets executed. The results are enclosed in << and >> to create a dictionary.



    The first dictionary defines everything to do with pairs including pairs-def which adds the key/value pairs into the current dictionary rather than begin a new dictionary. The next two sections add their functions to this same dictionary.



    This justifies (somewhat) the cuddled style of bracing. The whole implementation is split into 3 layers but the result is only 1 dictionary on the dictstack with all of these functions in it.



    The middle section defines block and func and all of the functionality for the simple-func style of defining functions. The third section implements two looping control structures which use the simple-func style. These looping functions are then used by the more complex typed-func style.



    Many of the functions used to implement all of this are useful in their own right so they are also supplied to the user, like curry compose reduce.



    There is some simplistic testing code at the bottom guarded by /debug where. So if this file is simply run from another file, it will skip the testing code. But if the key debug is defined somewhere on the dictstack, then the testing code will execute. So with ghostscript, testing can be invoked with gs -ddebug struct2.ps.



    The testing code itself illustrates the overhead of the code added by func. For type checking, it adds a fair amount of code.



    %!
    % struct2.ps An enhanced PostScript syntax for defining functions with named,
    % type-checked arguments. Using @func within a block or other construct that uses
    % 'pairs' accomplishes a sort of compile-time macro expansion of the shorthand function description.
    <<
    /pairs-begin pairs begin
    /pairs-def pairs def forall
    /pairs << exch explode >>
    /explode @exec forall
    /@exec dup type /nametype eq exec-if-@ if
    /exec-if-@ dup dup length string cvs dup first (@) first eq exec@ pop ifelse
    /first 0 get /exec@ exch pop rest cvn cvx exec
    /rest 1 1 index length 1 sub getinterval
    >> begin
    block pairs-begin main end
    func 1 index type /stringtype eq typed-func simple-func ifelse
    simple-func func-begin end compose
    typed-func exch args-and-types reverse make-type-name map check-stack 3 1 roll
    exch simple-func compose
    func-begin exch reverse /args-begin load curry exch compose
    args-begin dup length dict begin exch def forall
    args-and-types /was_x false def [ exch each-specifier fortokens fix-last ] dup args exch types
    each-specifier dup xcheck /is_x exch def is_x was_x and null exch if /was_x is_x def
    fix-last counttomark 2 mod 1 eq null if
    check-stack pop 4 index cvlit cvx /stackunderflow signalerror curry compose
    /if cvx 2 array astore cvx check-count exch compose curry
    3 index cvlit cvx /typecheck signalerror curry
    /if cvx 2 array astore cvx check-types exch compose compose
    check-count dup length count 2 sub gt
    check-types dup length 1 add copy true exch check-type and forall exch pop not
    check-type dup null eq 3 -1 roll pop pop true 3 -1 roll type eq ifelse
    make-type-name dup type /nametype eq dup length 4 add string dup dup 4 2 roll cvs
    2 copy 0 exch putinterval length (type) putinterval cvn if
    args [ exch 2 0 get fortuple ]
    types [ exch 2 1 get fortuple ]
    map 1 index xcheck 3 1 roll [ 3 1 roll forall ] exch cvx if
    reduce exch dup first exch rest 3 -1 roll forall
    rreduce exch aload length 1 sub dup 3 add -1 roll repeat
    curry [ 3 1 roll forall ] cvx @pop
    dup length 1 add array dup 0 5 -1 roll put dup 1 4 -1 roll putinterval cvx
    compose 2 array astore cvx forall map @pop
    1 index length 1 index length add array dup 0 4 index putinterval
    dup 4 -1 roll length 4 -1 roll putinterval cvx
    reverse [ exch dup length 1 sub -1 0 2 copy get 3 1 roll pop for pop ]
    pairs-def
    fortokens src proc src token exch /src exch storeexitifelse proc loop @func
    fortuple a n p 0 n /a load length 1 sub
    /a exch /n getinterval /p exec load-if-literal-name map end for
    @func-begin
    load-if-literal-name dup type /nametype eq 1 index xcheck not and load if
    pairs-def

    /debug wherepopcurrentfile flushfileifelse

    - sub + add * mul %:= exch def += dup load 3 -1 roll + store

    var 2 3 @add


    f x y z x y z + * @func

    f' x y z x y z + * end @func-begin

    f'' z y xargs-begin x y z + * end


    g(x/integer y/integer z/real) x y z + * @func

    g'
    [/realtype/integertype/integertype]
    check-count pop /g cvx /stackunderflow signalerror if
    check-types /g cvx /typecheck signalerror if

    z y xargs-begin x y z + * end



    h(x y z) x y z + * @func %@dup @==

    h'
    [null null null]
    check-count pop /h cvx /stackunderflow signalerror if
    check-types /h cvx /typecheck signalerror if

    z y xargs-begin x y z + * end



    main
    var ==
    [ 1 2 3 4 5 ] - rreduce ==
    / =
    3 4 5 f ==
    3 4 5 f' ==
    3 4 5 f'' ==
    / =
    3 4 5.0 g =
    3 4 5.0 g' =
    3 4 5 g = stopped $error /errorname get =only ( in ) print $error /command get = if
    / =
    clear
    3 4 h = stopped $error /errorname get =only ( in ) print $error /command get = if
    clear
    3 4 5 h =
    3.0 4.0 5.0 h = stopped $error /errorname get =only ( in ) print $error /command get = if
    3.0 4.0 5.0 h' = stopped $error /errorname get =only ( in ) print $error /command get = if
    quit


    block


    The output from the testing code:



    $ gsnd -q -ddebug struct2.ps
    5
    3

    27
    27
    27

    27.0
    27.0
    typecheck in g

    stackunderflow in h
    27
    27.0
    27.0


    Are there improvements to make to the implementation or the behavior? Currently the simple-func style does not check that there are enough arguments but just tries to define them assuming that they're there. Would it be better to add this checking, or is it better to have this low-overhead version which does not add (possibly wasteful) checks?







    share|improve this question





















      up vote
      2
      down vote

      favorite









      up vote
      2
      down vote

      favorite











      This file enhances PostScript with a few new syntactic niceties for defining functions with named arguments and even type-checking.



      block



      The first part of the enhancement consists of a new control structure called a "block". A block is a list of pairs which will be collected as key/value pairs into a dictionary and then the special key main gets called. This much allows us to elide the 'def' for all functions and data, and also the / decorations on all the function names.




      main f g h
      f
      g
      h
      block


      This code defines a main function and 3 functions that main calls. When block executes, all 4 functions are defined in a dictionary and then main gets called.



      Importantly, it lets you put main at the top to aid top-down coding.



      func



      Another enhancement is the func syntax. A function can be created to accept named arguments by calling func with the array of argument names and the array of the body of the function. To do this within the block construct where normally the contents of the block are not executed but just collected, you can force execution by prefixing the @ sign to func. Any name can be executed "at compile time" with this @ prefix, but only at the top-level.



      Using func you can give names to the arguments of functions by enclosing them in curly braces before the function body:




      main
      3 f
      4 5 g

      f x 1 x div sin @func
      g x y x log y log mul @func
      block


      This code creates a function f of one argument x, and a function g of two arguments x and y. The function body is augmented with code which takes these 2 objects from the stack and defines then in a new local dictionary created and begined for this invocation of the function coupled with an end at the end.



      For some use-cases like implementing control structures, it is useful to place the end more strategically. The fortuple function illustrates this by using @func-begin which does not add end at the end. It then places the end earlier, before calling back to the p argument.



      A function can also declare the types that its arguments must have by enclosing them in parentheses and using executable names for the argument names and literal names for the types:




      main
      3 4 p

      p (x/integer y/integer) x y add @func
      block


      This augments the function body with code which checks that there are indeed enough objects on the stack, or else it triggers a stackunderflow error. Then it checks that all of the arguments have the expected type, or else it triggers a typecheck error. The types here are written without the letters type at the end; these are added automatically.



      You can omit the type name with the parenthesized syntax and it will allow any type for that argument. If you omit all the type names you still get the stackunderflow checking. With any of these errors the name of the user function is reported in the error message for easier debugging.



      Implementation



      Implementation-wise, the foundation is the pairs construct which is an array which gets traversed with forall. Any name beginning with @ gets the @ stripped off and the remainder gets executed. The results are enclosed in << and >> to create a dictionary.



      The first dictionary defines everything to do with pairs including pairs-def which adds the key/value pairs into the current dictionary rather than begin a new dictionary. The next two sections add their functions to this same dictionary.



      This justifies (somewhat) the cuddled style of bracing. The whole implementation is split into 3 layers but the result is only 1 dictionary on the dictstack with all of these functions in it.



      The middle section defines block and func and all of the functionality for the simple-func style of defining functions. The third section implements two looping control structures which use the simple-func style. These looping functions are then used by the more complex typed-func style.



      Many of the functions used to implement all of this are useful in their own right so they are also supplied to the user, like curry compose reduce.



      There is some simplistic testing code at the bottom guarded by /debug where. So if this file is simply run from another file, it will skip the testing code. But if the key debug is defined somewhere on the dictstack, then the testing code will execute. So with ghostscript, testing can be invoked with gs -ddebug struct2.ps.



      The testing code itself illustrates the overhead of the code added by func. For type checking, it adds a fair amount of code.



      %!
      % struct2.ps An enhanced PostScript syntax for defining functions with named,
      % type-checked arguments. Using @func within a block or other construct that uses
      % 'pairs' accomplishes a sort of compile-time macro expansion of the shorthand function description.
      <<
      /pairs-begin pairs begin
      /pairs-def pairs def forall
      /pairs << exch explode >>
      /explode @exec forall
      /@exec dup type /nametype eq exec-if-@ if
      /exec-if-@ dup dup length string cvs dup first (@) first eq exec@ pop ifelse
      /first 0 get /exec@ exch pop rest cvn cvx exec
      /rest 1 1 index length 1 sub getinterval
      >> begin
      block pairs-begin main end
      func 1 index type /stringtype eq typed-func simple-func ifelse
      simple-func func-begin end compose
      typed-func exch args-and-types reverse make-type-name map check-stack 3 1 roll
      exch simple-func compose
      func-begin exch reverse /args-begin load curry exch compose
      args-begin dup length dict begin exch def forall
      args-and-types /was_x false def [ exch each-specifier fortokens fix-last ] dup args exch types
      each-specifier dup xcheck /is_x exch def is_x was_x and null exch if /was_x is_x def
      fix-last counttomark 2 mod 1 eq null if
      check-stack pop 4 index cvlit cvx /stackunderflow signalerror curry compose
      /if cvx 2 array astore cvx check-count exch compose curry
      3 index cvlit cvx /typecheck signalerror curry
      /if cvx 2 array astore cvx check-types exch compose compose
      check-count dup length count 2 sub gt
      check-types dup length 1 add copy true exch check-type and forall exch pop not
      check-type dup null eq 3 -1 roll pop pop true 3 -1 roll type eq ifelse
      make-type-name dup type /nametype eq dup length 4 add string dup dup 4 2 roll cvs
      2 copy 0 exch putinterval length (type) putinterval cvn if
      args [ exch 2 0 get fortuple ]
      types [ exch 2 1 get fortuple ]
      map 1 index xcheck 3 1 roll [ 3 1 roll forall ] exch cvx if
      reduce exch dup first exch rest 3 -1 roll forall
      rreduce exch aload length 1 sub dup 3 add -1 roll repeat
      curry [ 3 1 roll forall ] cvx @pop
      dup length 1 add array dup 0 5 -1 roll put dup 1 4 -1 roll putinterval cvx
      compose 2 array astore cvx forall map @pop
      1 index length 1 index length add array dup 0 4 index putinterval
      dup 4 -1 roll length 4 -1 roll putinterval cvx
      reverse [ exch dup length 1 sub -1 0 2 copy get 3 1 roll pop for pop ]
      pairs-def
      fortokens src proc src token exch /src exch storeexitifelse proc loop @func
      fortuple a n p 0 n /a load length 1 sub
      /a exch /n getinterval /p exec load-if-literal-name map end for
      @func-begin
      load-if-literal-name dup type /nametype eq 1 index xcheck not and load if
      pairs-def

      /debug wherepopcurrentfile flushfileifelse

      - sub + add * mul %:= exch def += dup load 3 -1 roll + store

      var 2 3 @add


      f x y z x y z + * @func

      f' x y z x y z + * end @func-begin

      f'' z y xargs-begin x y z + * end


      g(x/integer y/integer z/real) x y z + * @func

      g'
      [/realtype/integertype/integertype]
      check-count pop /g cvx /stackunderflow signalerror if
      check-types /g cvx /typecheck signalerror if

      z y xargs-begin x y z + * end



      h(x y z) x y z + * @func %@dup @==

      h'
      [null null null]
      check-count pop /h cvx /stackunderflow signalerror if
      check-types /h cvx /typecheck signalerror if

      z y xargs-begin x y z + * end



      main
      var ==
      [ 1 2 3 4 5 ] - rreduce ==
      / =
      3 4 5 f ==
      3 4 5 f' ==
      3 4 5 f'' ==
      / =
      3 4 5.0 g =
      3 4 5.0 g' =
      3 4 5 g = stopped $error /errorname get =only ( in ) print $error /command get = if
      / =
      clear
      3 4 h = stopped $error /errorname get =only ( in ) print $error /command get = if
      clear
      3 4 5 h =
      3.0 4.0 5.0 h = stopped $error /errorname get =only ( in ) print $error /command get = if
      3.0 4.0 5.0 h' = stopped $error /errorname get =only ( in ) print $error /command get = if
      quit


      block


      The output from the testing code:



      $ gsnd -q -ddebug struct2.ps
      5
      3

      27
      27
      27

      27.0
      27.0
      typecheck in g

      stackunderflow in h
      27
      27.0
      27.0


      Are there improvements to make to the implementation or the behavior? Currently the simple-func style does not check that there are enough arguments but just tries to define them assuming that they're there. Would it be better to add this checking, or is it better to have this low-overhead version which does not add (possibly wasteful) checks?







      share|improve this question











      This file enhances PostScript with a few new syntactic niceties for defining functions with named arguments and even type-checking.



      block



      The first part of the enhancement consists of a new control structure called a "block". A block is a list of pairs which will be collected as key/value pairs into a dictionary and then the special key main gets called. This much allows us to elide the 'def' for all functions and data, and also the / decorations on all the function names.




      main f g h
      f
      g
      h
      block


      This code defines a main function and 3 functions that main calls. When block executes, all 4 functions are defined in a dictionary and then main gets called.



      Importantly, it lets you put main at the top to aid top-down coding.



      func



      Another enhancement is the func syntax. A function can be created to accept named arguments by calling func with the array of argument names and the array of the body of the function. To do this within the block construct where normally the contents of the block are not executed but just collected, you can force execution by prefixing the @ sign to func. Any name can be executed "at compile time" with this @ prefix, but only at the top-level.



      Using func you can give names to the arguments of functions by enclosing them in curly braces before the function body:




      main
      3 f
      4 5 g

      f x 1 x div sin @func
      g x y x log y log mul @func
      block


      This code creates a function f of one argument x, and a function g of two arguments x and y. The function body is augmented with code which takes these 2 objects from the stack and defines then in a new local dictionary created and begined for this invocation of the function coupled with an end at the end.



      For some use-cases like implementing control structures, it is useful to place the end more strategically. The fortuple function illustrates this by using @func-begin which does not add end at the end. It then places the end earlier, before calling back to the p argument.



      A function can also declare the types that its arguments must have by enclosing them in parentheses and using executable names for the argument names and literal names for the types:




      main
      3 4 p

      p (x/integer y/integer) x y add @func
      block


      This augments the function body with code which checks that there are indeed enough objects on the stack, or else it triggers a stackunderflow error. Then it checks that all of the arguments have the expected type, or else it triggers a typecheck error. The types here are written without the letters type at the end; these are added automatically.



      You can omit the type name with the parenthesized syntax and it will allow any type for that argument. If you omit all the type names you still get the stackunderflow checking. With any of these errors the name of the user function is reported in the error message for easier debugging.



      Implementation



      Implementation-wise, the foundation is the pairs construct which is an array which gets traversed with forall. Any name beginning with @ gets the @ stripped off and the remainder gets executed. The results are enclosed in << and >> to create a dictionary.



      The first dictionary defines everything to do with pairs including pairs-def which adds the key/value pairs into the current dictionary rather than begin a new dictionary. The next two sections add their functions to this same dictionary.



      This justifies (somewhat) the cuddled style of bracing. The whole implementation is split into 3 layers but the result is only 1 dictionary on the dictstack with all of these functions in it.



      The middle section defines block and func and all of the functionality for the simple-func style of defining functions. The third section implements two looping control structures which use the simple-func style. These looping functions are then used by the more complex typed-func style.



      Many of the functions used to implement all of this are useful in their own right so they are also supplied to the user, like curry compose reduce.



      There is some simplistic testing code at the bottom guarded by /debug where. So if this file is simply run from another file, it will skip the testing code. But if the key debug is defined somewhere on the dictstack, then the testing code will execute. So with ghostscript, testing can be invoked with gs -ddebug struct2.ps.



      The testing code itself illustrates the overhead of the code added by func. For type checking, it adds a fair amount of code.



      %!
      % struct2.ps An enhanced PostScript syntax for defining functions with named,
      % type-checked arguments. Using @func within a block or other construct that uses
      % 'pairs' accomplishes a sort of compile-time macro expansion of the shorthand function description.
      <<
      /pairs-begin pairs begin
      /pairs-def pairs def forall
      /pairs << exch explode >>
      /explode @exec forall
      /@exec dup type /nametype eq exec-if-@ if
      /exec-if-@ dup dup length string cvs dup first (@) first eq exec@ pop ifelse
      /first 0 get /exec@ exch pop rest cvn cvx exec
      /rest 1 1 index length 1 sub getinterval
      >> begin
      block pairs-begin main end
      func 1 index type /stringtype eq typed-func simple-func ifelse
      simple-func func-begin end compose
      typed-func exch args-and-types reverse make-type-name map check-stack 3 1 roll
      exch simple-func compose
      func-begin exch reverse /args-begin load curry exch compose
      args-begin dup length dict begin exch def forall
      args-and-types /was_x false def [ exch each-specifier fortokens fix-last ] dup args exch types
      each-specifier dup xcheck /is_x exch def is_x was_x and null exch if /was_x is_x def
      fix-last counttomark 2 mod 1 eq null if
      check-stack pop 4 index cvlit cvx /stackunderflow signalerror curry compose
      /if cvx 2 array astore cvx check-count exch compose curry
      3 index cvlit cvx /typecheck signalerror curry
      /if cvx 2 array astore cvx check-types exch compose compose
      check-count dup length count 2 sub gt
      check-types dup length 1 add copy true exch check-type and forall exch pop not
      check-type dup null eq 3 -1 roll pop pop true 3 -1 roll type eq ifelse
      make-type-name dup type /nametype eq dup length 4 add string dup dup 4 2 roll cvs
      2 copy 0 exch putinterval length (type) putinterval cvn if
      args [ exch 2 0 get fortuple ]
      types [ exch 2 1 get fortuple ]
      map 1 index xcheck 3 1 roll [ 3 1 roll forall ] exch cvx if
      reduce exch dup first exch rest 3 -1 roll forall
      rreduce exch aload length 1 sub dup 3 add -1 roll repeat
      curry [ 3 1 roll forall ] cvx @pop
      dup length 1 add array dup 0 5 -1 roll put dup 1 4 -1 roll putinterval cvx
      compose 2 array astore cvx forall map @pop
      1 index length 1 index length add array dup 0 4 index putinterval
      dup 4 -1 roll length 4 -1 roll putinterval cvx
      reverse [ exch dup length 1 sub -1 0 2 copy get 3 1 roll pop for pop ]
      pairs-def
      fortokens src proc src token exch /src exch storeexitifelse proc loop @func
      fortuple a n p 0 n /a load length 1 sub
      /a exch /n getinterval /p exec load-if-literal-name map end for
      @func-begin
      load-if-literal-name dup type /nametype eq 1 index xcheck not and load if
      pairs-def

      /debug wherepopcurrentfile flushfileifelse

      - sub + add * mul %:= exch def += dup load 3 -1 roll + store

      var 2 3 @add


      f x y z x y z + * @func

      f' x y z x y z + * end @func-begin

      f'' z y xargs-begin x y z + * end


      g(x/integer y/integer z/real) x y z + * @func

      g'
      [/realtype/integertype/integertype]
      check-count pop /g cvx /stackunderflow signalerror if
      check-types /g cvx /typecheck signalerror if

      z y xargs-begin x y z + * end



      h(x y z) x y z + * @func %@dup @==

      h'
      [null null null]
      check-count pop /h cvx /stackunderflow signalerror if
      check-types /h cvx /typecheck signalerror if

      z y xargs-begin x y z + * end



      main
      var ==
      [ 1 2 3 4 5 ] - rreduce ==
      / =
      3 4 5 f ==
      3 4 5 f' ==
      3 4 5 f'' ==
      / =
      3 4 5.0 g =
      3 4 5.0 g' =
      3 4 5 g = stopped $error /errorname get =only ( in ) print $error /command get = if
      / =
      clear
      3 4 h = stopped $error /errorname get =only ( in ) print $error /command get = if
      clear
      3 4 5 h =
      3.0 4.0 5.0 h = stopped $error /errorname get =only ( in ) print $error /command get = if
      3.0 4.0 5.0 h' = stopped $error /errorname get =only ( in ) print $error /command get = if
      quit


      block


      The output from the testing code:



      $ gsnd -q -ddebug struct2.ps
      5
      3

      27
      27
      27

      27.0
      27.0
      typecheck in g

      stackunderflow in h
      27
      27.0
      27.0


      Are there improvements to make to the implementation or the behavior? Currently the simple-func style does not check that there are enough arguments but just tries to define them assuming that they're there. Would it be better to add this checking, or is it better to have this low-overhead version which does not add (possibly wasteful) checks?









      share|improve this question










      share|improve this question




      share|improve this question









      asked May 3 at 1:36









      luser droog

      803522




      803522

























          active

          oldest

          votes











          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193520%2fan-enhanced-syntax-for-defining-functions-in-postscript%23new-answer', 'question_page');

          );

          Post as a guest



































          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes










           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193520%2fan-enhanced-syntax-for-defining-functions-in-postscript%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Python Lists

          Aion

          JavaScript Array Iteration Methods