An enhanced syntax for defining functions in PostScript

Clash 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?
postscript
add a comment |Â
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?
postscript
add a comment |Â
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?
postscript
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?
postscript
asked May 3 at 1:36
luser droog
803522
803522
add a comment |Â
add a comment |Â
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password