Reputation: 18577
I'm trying to wrap my head around the immediate and deferred execution. From what I understand is that the interpreter maintains a flag that knows if it in deferred execution or not.
Deferred execution of a procedure could be because a name lookup returned a procedure.
Now I'm trying to find out what types, actions or operations control this interpreter flag.
For example this piece of code below has an immediately evaluated name at the end which returns an procedure. But this procedure is pushed, while it it is executable (xcheck):
/setdata
{
/a 1 def
/b 0 def
/foo
a 0 ne
b 0 ne
and
def
{ foo false and }
} def
//setdata
I know there is a special rule:
Procedures appearing directly (either as part of a program being read from a file or as part of some larger procedure in memory) are usually part of a definition or of a construct, such as a conditional, that operates on the procedure explicitly. But procedures obtained indirectly—for example, as a result of looking up a name—are usually intended to be executed. A PostScript program can override these semantics when necessary.
I understand that if you encounter a procedure directly that you have to push it (even if it is executable). (The immediately evaluated name returns a procedure, which is encountered directly, so it should be pushed to the OS.)
Now if I'm thinking in code to implement this logic in an interpreter I can think of something like this:
If I have a literalname lookup, set the interpreter's DeferredFlag = true; Now how do I know when the deferred execution ends? I can hardcode if I encounter the "def" name, but there might be others.
(+ What in case procedures are nested in a procedure that is executing. etc...)
I can't find a way to control that DeferredFlag in the interpreter to know the current execution mode.
Hope the question is clear.
Update:
Some extra code samples I try to debug without success.
code 1:
/foo { 2 3 add } def
foo
% result: 5
code 2:
/foo { 2 3 add } def
//foo
% result: { 2 3 add }
code 3:
/foo { 2 3 add } def
/bar { foo } def
bar
% result: 5
code 4:
/foo { 2 3 add } def
/bar { //foo } def
bar
% result: { 2 3 add }
Upvotes: 4
Views: 324
Reputation: 19504
I had many of the same questions and confusions when trying to understand the interpreter. IMO the term deferred execution is not very useful. Additionally, I think the term immediately evaluated is also not very useful. You don't need a DeferredFlag.
There are two separate but related piece involved here: the interpreter loop, and the token
operator.
token
handles the part of "deferred execution" which collects all the tokens of an executable array into a single object. So if a file or string starts with a procedure body, then calling token
on it yields the whole procedure body.
{ execution is deferred until the closing }
It looks like a comment, but that is a line of postscript code which will run without errors even if none of the words deferred, closing, the etc. are defined. If you call exec
on it, however, or define it as a name an call that, then it will execute and the contents had better be defined.
The interpreter loop always grabs the top object from the exec stack and semantically, executable arrays, files, and strings all behave the same. The interpreter treats it as a source and gets the first element. The name case is a little different, because it's not a source per se. (I am introducing this concept of my own hoping that it helps/works.) In C-ish pseudocode:
main_loop(){
while( ! quit ){
eval();
}
}
eval(){
object = pop( exec_stack );
if( !executable_flag( object ) ) push( op_stack, object );
else switch( type_of( object ) ){
case array: array_handler( object ); break;
case string: string_handler( object ); break;
case file: file_handler( object ); break;
case name: name_handler( object ); break;
default: push( op_stack, object );
}
}
In the name case, look up the name and execute if executable.
name_handler( object ) {
object = load( object );
push( executable_flag( object ) ? exec_stack : op_stack, object );
}
In the other three, you must also check if it is an array.
array_handler( object ){
switch( length( object ){
default:
push( exec_stack, getinterval( object, 1, length( object ) - 1 ) );
/* fall-thru */
case 1:
object = get( object, 0 );
push( executable_flag( object ) && type_of( object ) != array ?
exec_stack : op_stack, object );
case 0:
/* do nothing */
}
}
Only if executable_flag( object ) && type_of( object ) != array
then you push to the exec stack.
For the other question, immediately-evaluated names, I prefer to call them immediately-loaded names. The token
operator calls load
on it before returning. Easy to handle if done in the right place. It has no real interaction with the "deferred-execution" part.
Edit:
I ran your sample through my debugger with tracing. This shows a running picture of the op_stack after each token is executed. The element on the left is the object returned by token
. Notice that token
has already consumed all the //
s.
$ cat test.ps
(db5.ps) run currentfile cvx traceon debug
/foo { 2 3 add } def
foo
% result: 5
/foo { 2 3 add } def
//foo
% result: { 2 3 add }
/foo { 2 3 add } def
/bar { foo } def
bar
% result: 5
/foo { 2 3 add } def
/bar { //foo } def
bar
% result: { 2 3 add }
$ gsnd -DNOSAFER test.ps
GPL Ghostscript 9.19 (2016-03-23)
Copyright (C) 2016 Artifex Software, Inc. All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
%|-
/foo %|- /foo
{2 3 add} %|- /foo {2 3 add}
def %|-
foo %|- 5
/foo %|- 5 /foo
{2 3 add} %|- 5 /foo {2 3 add}
def %|- 5
{2 3 add} %|- 5 {2 3 add}
/foo %|- 5 {2 3 add} /foo
{2 3 add} %|- 5 {2 3 add} /foo {2 3 add}
def %|- 5 {2 3 add}
/bar %|- 5 {2 3 add} /bar
{foo} %|- 5 {2 3 add} /bar {foo}
def %|- 5 {2 3 add}
bar %|- 5 {2 3 add} 5
/foo %|- 5 {2 3 add} 5 /foo
{2 3 add} %|- 5 {2 3 add} 5 /foo {2 3 add}
def %|- 5 {2 3 add} 5
/bar %|- 5 {2 3 add} 5 /bar
{{2 3 add}} %|- 5 {2 3 add} 5 /bar {{2 3 add}}
def %|- 5 {2 3 add} 5
bar GS<4>
GS<4>pstack
{2 3 add}
5
{2 3 add}
5
GS<4>
Upvotes: 6