I've written Forth code (below) to demonstrate keeping track of word dependencies using a Forth system capable of defining dual-semantics
words. Here I use Gforth for the example. I believe the following may
also be doable on VFX, using its NDCS: word. The other requirements of
the system are that the name token (nt) of the last word definition is accessible, and that there is a mechanism to define a closure.
A word definition may be immediately followed by the word TRACK-DEPS
which will set its compilation semantics to not only compile its interpretation semantics but also to add an entry into a dependency tree
to keep track of the words into which it is compiled. For example,
: FOO ." foo" ; TRACK-DEPS
: T1 foo ;
The word SHOW-DEPS prints the names of all words in which FOO is compiled.
s" foo" SHOW-DEPS \ prints T1
To continue,
: BAR ." bar" ; TRACK-DEPS
: T2 foo bar ;
s" foo" SHOW-DEPS \ prints the list: T2 T1
s" bar" SHOW-DEPS \ prints the list: T2
With the other definitions in the example code, it should be possible to follow the entire dependency tree branch which terminates on a word for
which TRACK-DEPS has been applied (this assumes all of the nodes leading
to the word are also being tracked).
For simplicity, the dependency tree is implemented as a fixed size
table. This is not memory efficient and flexible enough for real use,
but is suitable for a demonstration. A tree structure should be used for actual applications. The main point is to show that being able to set
the compilation semantics for a word independently allows for
capabilities which are likely not possible (or considerably more
difficult) in Forth source on a single-xt + immediate flag system only.
--
Krishna Myneni
dependencies.4th
-----------------
\ dependencies.4th
\
\ Demonstrate tracking first-level word dependencies
\ in a dual-xt system.
\
\ K. Myneni, 2022-10-10
\
\ This program is written for Gforth. It uses the following
\ non-standard words
\
\ LATESTNT ( -- nt )
\ SET-COMPSEM ( xt -- )
\ [N:D ( x -- )
require set-compsem.fs
synonym a@ @
100 constant MAX-TRACKS
64 constant MAX-DEPS
MAX-TRACKS MAX-DEPS * cells constant MAX-DTABLE-SIZE
create DEPENDENCY-TABLE MAX-DTABLE-SIZE allot
DEPENDENCY-TABLE MAX-DTABLE-SIZE erase
: th-track ( u -- a )
DEPENDENCY-TABLE swap MAX-DEPS * cells + ;
0 value curr-track
: get-track ( nt -- n ) \ return -1 if not found
curr-track 0 ?DO
I th-track a@ \ nt nt-track
over = IF drop I UNLOOP EXIT THEN
LOOP
drop -1 ;
: track-dependency ( nt -- )
curr-track th-track !
1 curr-track + to curr-track ;
: add-dependency ( nt-track nt-newdep -- )
swap get-track dup 0< IF
." Not tracking." cr abort
ELSE
th-track \ -- nt-dep a
BEGIN cell+ dup a@ 0= UNTIL !
THEN ;
\ Track first-level dependencies for last defined word
: track-deps ( -- )
latestnt dup track-dependency
[n:d dup name>interpret compile, latestnt add-dependency ;]
set-compsem ;
0 value ndeps
: what-requires? ( nt-track -- nt1 nt2 ... u | 0 )
0 to ndeps
get-track dup 0< IF
drop 0
ELSE
th-track cell+ \ -- a
BEGIN
dup a@ dup \ -- a nt|0 nt|0
WHILE
swap
ndeps 1+ to ndeps
cell+
REPEAT
2drop ndeps
THEN ;
: show-deps ( caddr u -- )
find-name ?dup IF
what-requires?
0 ?DO cr name>string type LOOP cr
then ;
0 [IF] \ Demonstration
\ Define two words for which first-level deps will be tracked
: foo ." foo" ; track-deps
: bar ." bar" ; track-deps
foo cr
bar cr
s" foo" show-deps \ list of words dependent on FOO is empty
s" bar" show-deps \ list of words dependent on BAR is empty
: t1 foo ;
: t2 foo bar ;
s" foo" show-deps \ shows T1 and T2 are dependent on FOO
s" bar" show-deps \ shows T2 is dependent on BAR
[THEN]
Krishna Myneni schrieb am Montag, 10. Oktober 2022 um 17:44:24 UTC+2:..
I've written Forth code (below) to demonstrate keeping track of word
dependencies using a Forth system capable of defining dual-semantics
words.
FWIW I use a dependency table for recursive elimination of
dead words (ie those not needed for an application).
But back to your proposed dependency tracker:
a) a.m. situation for dual-semantic parsing words applies as well?
b) what is your benefit or use case for dependency introspection?
Krishna Myneni schrieb am Montag, 10. Oktober 2022 um 17:44:24 UTC+2:..
I've written Forth code (below) to demonstrate keeping track of word
dependencies using a Forth system capable of defining dual-semantics
words. Here I use Gforth for the example.
.\ Track first-level dependencies for last defined word
: track-deps ( -- )
latestnt dup track-dependency
[n:d dup name>interpret compile, latestnt add-dependency ;]
set-compsem ;
But back to your proposed dependency tracker:
a) a.m. situation for dual-semantic parsing words applies as well?
b) what is your benefit or use case for dependency introspection?
Indeed, in the version of
TRACK-DEPS shown above, the code does not work for immediate words. I
found I had to revise TRACK-DEPS as follows to make it work in Gforth
for both precedence cases:
\ Track first-level dependencies for last defined word
: track-deps ( -- )
latestnt dup track-dependency ( -- nt-track)
dup name>compile nip
['] compile, = if
[n:d dup name>interpret compile, latestnt add-dependency ;]
else
[n:d dup name>interpret execute latestnt add-dependency ;]
then
set-compsem ;
I don't understand why the following simpler definition of TRACK-DEPS
does not work under Gforth (0.7.9_20220120):
: track-deps ( -- )
latestnt dup track-dependency ( -- nt-track)
[n:d dup name>compile execute latestnt add-dependency ;]
set-compsem ;
The above definition, which (I thought) should be equivalent to the
working definition shown above leads to some sort of infinite recursion >resulting in a return stack overflow in Gforth v. 0.7.9_20220120.
: foo ." foo" ; track-deps ok
: t1 foo ;
*the terminal*:3:6: error: Return stack overflow
: t1 >>>foo<<< ;
Krishna Myneni <krishna.myneni@ccreweb.org> writes:
Indeed, in the version of
TRACK-DEPS shown above, the code does not work for immediate words. I
found I had to revise TRACK-DEPS as follows to make it work in Gforth
for both precedence cases:
\ Track first-level dependencies for last defined word
: track-deps ( -- )
latestnt dup track-dependency ( -- nt-track)
dup name>compile nip
['] compile, = if
[n:d dup name>interpret compile, latestnt add-dependency ;]
else
[n:d dup name>interpret execute latestnt add-dependency ;]
then
set-compsem ;
The NAME>INTERPRETs here should be left away: You pass an xt to the
closure, not an nt. Fortunately (or unfortunately), in Gforth an xt
can be treated as an nt, so this mistake does not result in unintended behaviour, at least for most words.
I don't understand why the following simpler definition of TRACK-DEPS
does not work under Gforth (0.7.9_20220120):
: track-deps ( -- )
latestnt dup track-dependency ( -- nt-track)
[n:d dup name>compile execute latestnt add-dependency ;]
set-compsem ;
The above definition, which (I thought) should be equivalent to the
working definition shown above leads to some sort of infinite recursion
resulting in a return stack overflow in Gforth v. 0.7.9_20220120.
: foo ." foo" ; track-deps ok
: t1 foo ;
*the terminal*:3:6: error: Return stack overflow
: t1 >>>foo<<< ;
SET-COMPSEM changes what NAME>COMPILE does for the word, and the end
result is that the NAME>COMPILE EXECUTE inside the closure calls the
closure again, which calls NAME>COMPILE EXECUTE again until the return
stack overflows.
On 10/12/22 11:55, Anton Ertl wrote:
Krishna Myneni <krishna.myneni@ccreweb.org> writes:
Indeed, in the version of
TRACK-DEPS shown above, the code does not work for immediate words. I
found I had to revise TRACK-DEPS as follows to make it work in Gforth
for both precedence cases:
\ Track first-level dependencies for last defined word
: track-deps ( -- )
latestnt dup track-dependency ( -- nt-track)
dup name>compile nip
['] compile, = if
[n:d dup name>interpret compile, latestnt add-dependency ;]
else
[n:d dup name>interpret execute latestnt add-dependency ;]
then
set-compsem ;
The NAME>INTERPRETs here should be left away: You pass an xt to the
closure, not an nt. Fortunately (or unfortunately), in Gforth an xt
can be treated as an nt, so this mistake does not result in unintended
behaviour, at least for most words.
An nt is being passed into the closure, so the NAME>INTERPRET is needed >inside the closure. Note the DUP before NAME>COMPILE and the NIP afterwards.
I don't understand why the following simpler definition of TRACK-DEPS
does not work under Gforth (0.7.9_20220120):
: track-deps ( -- )
latestnt dup track-dependency ( -- nt-track)
[n:d dup name>compile execute latestnt add-dependency ;]
set-compsem ;
The above definition, which (I thought) should be equivalent to the
working definition shown above leads to some sort of infinite recursion
resulting in a return stack overflow in Gforth v. 0.7.9_20220120.
: foo ." foo" ; track-deps ok
: t1 foo ;
*the terminal*:3:6: error: Return stack overflow
: t1 >>>foo<<< ;
SET-COMPSEM changes what NAME>COMPILE does for the word, and the end
result is that the NAME>COMPILE EXECUTE inside the closure calls the
closure again, which calls NAME>COMPILE EXECUTE again until the return
stack overflows.
It's too bad that SET-COMPSEM doesn't simply create a new code sequence
for the compilation semantics and store the corresponding xt. This is
similar to the problem with SET-OPTIMIZER. These words should not change
the behavior of "COMPILE," or of "NAME>COMPILE". Doing so makes these >standard words non-transparent to the programmer, resulting in
unexpected problems like this.
Krishna Myneni <krishna.myneni@ccreweb.org> writes:
On 10/12/22 11:55, Anton Ertl wrote:
Krishna Myneni <krishna.myneni@ccreweb.org> writes:
Indeed, in the version of
TRACK-DEPS shown above, the code does not work for immediate words. I
found I had to revise TRACK-DEPS as follows to make it work in Gforth
for both precedence cases:
\ Track first-level dependencies for last defined word
: track-deps ( -- )
latestnt dup track-dependency ( -- nt-track)
dup name>compile nip
['] compile, = if
[n:d dup name>interpret compile, latestnt add-dependency ;]
else
[n:d dup name>interpret execute latestnt add-dependency ;]
then
set-compsem ;
The NAME>INTERPRETs here should be left away: You pass an xt to the
closure, not an nt. Fortunately (or unfortunately), in Gforth an xt
can be treated as an nt, so this mistake does not result in unintended
behaviour, at least for most words.
An nt is being passed into the closure, so the NAME>INTERPRET is needed
inside the closure. Note the DUP before NAME>COMPILE and the NIP afterwards.
NAME>COMPILE produces xt1 xt2; you consume xt2 in "['] COMPILE, =",
leaving xt1, which you then pass into a closure. You may expect an nt
and treat it like an nt (and it happens to work to some extent), but conceptually it's an xt; if we changed Gforth so that an xt would not
be a valid xt, this code would break very obviously.
As it is, you may see some lossage in connection with, e.g., synonyms.
E.g., try
: a ." a" ;
synonym b a track-deps
: c b ;
s" b" show-deps
Does it show that c depends on b?
I don't understand why the following simpler definition of TRACK-DEPS
does not work under Gforth (0.7.9_20220120):
: track-deps ( -- )
latestnt dup track-dependency ( -- nt-track)
[n:d dup name>compile execute latestnt add-dependency ;]
set-compsem ;
The above definition, which (I thought) should be equivalent to the
working definition shown above leads to some sort of infinite recursion >>>> resulting in a return stack overflow in Gforth v. 0.7.9_20220120.
: foo ." foo" ; track-deps ok
: t1 foo ;
*the terminal*:3:6: error: Return stack overflow
: t1 >>>foo<<< ;
SET-COMPSEM changes what NAME>COMPILE does for the word, and the end
result is that the NAME>COMPILE EXECUTE inside the closure calls the
closure again, which calls NAME>COMPILE EXECUTE again until the return
stack overflows.
It's too bad that SET-COMPSEM doesn't simply create a new code sequence
for the compilation semantics and store the corresponding xt. This is
similar to the problem with SET-OPTIMIZER. These words should not change
the behavior of "COMPILE," or of "NAME>COMPILE". Doing so makes these
standard words non-transparent to the programmer, resulting in
unexpected problems like this.
If SET-OPTIMIZER would not change COMPILE,, it would not work (it
would fail to set the optimizer). For Gforth's current
implementation, if SET-COMPSEM did not change NAME>COMPILE, it would
not work (it would fail to change the compilation semantics).
But these problems are just like any other case where you
inadvertently introduce endless recursion; e.g., such endless
recursions happen easily if you try to reduce the number of primitives
by replacing them with colon definitions in a cross-compiler that
allows forward references. You just have to break the cycle, in the
present case by explicitly performing the old action of NAME>COMPILE
instead of just calling NAME>COMPILE.
Concerning SET-COMPSEM, one could imagine an extended header structure
that also contains a COMPSEM field. SET-COMPSEM then would set that
field and would change NAME>COMPILE to perform:
: compsem-name>comp ( nt -- xt1 xt2 )
>namehm @ >hmcompsem @ ['] execute ;
But that still would not help with the code above, because
NAME>COMPILE still would give you the new compilation semantics, which
would again result in endless recursion.
This would solve your immediate problem, but it would mean that your extension would not compose with other extensions that use
SET-COMPSEM: E.g., if I have an extension that counts the number of
times the compilation semantics of this word is invoked, and then
apply your TRACK-DEPS extension to the word, the counting extension is disabled. If the counting extension also uses NAME>COMPILE-ORIGINAL
instead of the previous NAME>COMPILE-USER action, then applying it
after TRACK-DEPS would disable TRACK-DEPS.
Therefore I think that just invoking the old NAME>COMPILE result
explicitly is the way to go. It's a little more complicated, but
composable.
On 10/13/22 10:30, Anton Ertl wrote:
As it is, you may see some lossage in connection with, e.g., synonyms.
E.g., try
: a ." a" ;
synonym b a track-deps
: c b ;
s" b" show-deps
Does it show that c depends on b?
: track-deps ( -- )
latestnt dup track-dependency ( -- nt-track)
dup name>compile ( -- nt-track x xt )
3 cells allocate drop \ reserve memory for old compsem
dup >r 2! r@ 2 cells + ! r>
[n:d dup 2@ execute 2 cells + a@ latestnt add-dependency ;]
set-compsem ;
sqkeyHitQ?
On 10/13/22 10:30, Anton Ertl wrote:
If SET-OPTIMIZER would not change COMPILE,, it would not work (it
would fail to set the optimizer). For Gforth's current
implementation, if SET-COMPSEM did not change NAME>COMPILE, it would
not work (it would fail to change the compilation semantics).
In the case of standard "COMPILE," the problem is that on a dual-xt
system, "COMPILE," operating on an xt is a fundamentally incorrect way
of compiling a named definition (word).
I understand
your "hack" in Gforth is for the purpose of maintaining backwards >compatibility.
You just have to break the cycle, in the
present case by explicitly performing the old action of NAME>COMPILE
instead of just calling NAME>COMPILE.
Does SET-COMPSEM erase all memory of the original compilation semantics,
or is the old xt-comp still available? If not, we should be able to fix
this.
earlier does:COMP and execute that; that's what the untested version I posted
Ok, I see that there is a problem with using NAME>COMPILE within the
the closure's xt, passed to SET-COMPSEM.
NAME>COMPILE should be used outside the closure to obtain the
compilation semantics at the time that TRACK-DEPS was invoked. Thus a >permanent data structure, 3 cells long, may be passed into the closure.
The data structure contains the nt of the word, and the original
compilation semantics, which consists of two cells of data: x xt. Then
the closure can invoke both the original compilation semantics and
append the extra actions needed to add information to the dependency table.
Here's my new version of TRACK-DEPS. It invokes NAME>COMPILE outside of
the closure to obtain and store the current compilation semantics for
the word before SET-COMPSEM is invoked. It should satisfy all cases, >including preserving any extensions to the compilation semantics prior
to invoking TRACK-DEPS. I've tested it on the single-xt+immediate flag
cases. So it appears that the previous xt-comp is still valid.
: track-deps ( -- )
latestnt dup track-dependency ( -- nt-track)
dup name>compile ( -- nt-track x xt )
3 cells allocate drop \ reserve memory for old compsem
dup >r 2! r@ 2 cells + ! r>
[n:d dup 2@ execute 2 cells + a@ latestnt add-dependency ;]
set-compsem ;
It also gives different results for your dependency-tracking (which is >outside semantics, so you can use either mechanism). Consider:
: bar ." bar" ; track-deps
: foo postpone bar postpone bar ; immediate
: bla foo foo ;
: blub [ ' bar compile, ] ;
If you track dependencies throug NAME>COMPILE as mechanism, you will
see that BAR is used twice in FOO. If you track dependencies using >"COMPILE,", you will see that BAR is used four times in BLA and one
time in BLUB.
On 10/14/22 02:21, Anton Ertl wrote:
I don't know what you mean with '"hack"'. Forth has COMPILE, for
appending semantics to the current definition, and NAME>COMPILE for
getting the compilation semantics of a named word. These are two
different jobs, and Gforth provides SET-OPTIMIZER and SET->COMP,
respectively, to provide implementations for these words for the
current definition. And that's the right way to do it.
... "COMPILE," when used with NAME>INTERPRET, or
equivalently with '(tick) is fine when it is desired to append the interpretation semantics, but for appending the compilation semantics of
a "combined word", one should use NAME>COMPILE and EXECUTE. ...
Krishna Myneni <krishna.myneni@ccreweb.org> writes:
On 10/13/22 10:30, Anton Ertl wrote:
If SET-OPTIMIZER would not change COMPILE,, it would not work (it
would fail to set the optimizer). For Gforth's current
implementation, if SET-COMPSEM did not change NAME>COMPILE, it would
not work (it would fail to change the compilation semantics).
In the case of standard "COMPILE," the problem is that on a dual-xt
system, "COMPILE," operating on an xt is a fundamentally incorrect way
of compiling a named definition (word).
If you mean that, e.g.,
: [compile,] compile, ; immediate
: foo [ ' s" ] [compile,] bla" ;
is not (and must not be) equivalent to
: foo s" bla" ;
i.e., performing the compilation semantics of S", you are correct.
It's also incorrect for performing the compilation semantics of
: bar ." bar" ; immediate
But
: foo1 [ ' bar ] [compile,] ;
appends the execution=interpretation=compilation semantics to foo1, so
it is equivalent to
: foo1 postpone bar ;
Ticking named words is unambiguous for most named words, and
COMPILE,ing the resulting xt is just as standard as EXECUTEing it.
I understand
your "hack" in Gforth is for the purpose of maintaining backwards
compatibility.
I don't know what you mean with '"hack"'. Forth has COMPILE, for
appending semantics to the current definition, and NAME>COMPILE for
getting the compilation semantics of a named word. These are two
different jobs, and Gforth provides SET-OPTIMIZER and SET->COMP, respectively, to provide implementations for these words for the
current definition. And that's the right way to do it.
Because one often follows the other, there have been attempts to use
one mechanism for both purposes:
* It is incorrect to use SET-OPTIMIZER for changing the compilation semantics.
* If you use SET->COMP for changing the code generation, you don't
cover the cases that COMPILE, xts directly instead of going through NAME>COMPILE, and you have to provide a fallback mechanism for
COMPILE,ing that; and if you implement [COMPILE], you also have to
tell it which uses of SET->COMP change the default compilation
semantics and which don't. You may think that you are saving
complexity by eliminating SET-OPTIMIZER, but the complexity pops up elsewhere.
I think that providing both mechanisms is a good solution, because it
also provides a nice separation of concerns: If you want to change the generated code without changing the semantics (i.e., if you want to optimize), use SET-OPTIMIZER; if you want to change the compilation semantics, use SET->COMP (or, by extension, SET-COMPSEM).
It also gives different results for your dependency-tracking (which is outside semantics, so you can use either mechanism). Consider:
: bar ." bar" ; track-deps
: foo postpone bar postpone bar ; immediate
: bla foo foo ;
: blub [ ' bar compile, ] ;
If you track dependencies throug NAME>COMPILE as mechanism, you will
see that BAR is used twice in FOO. If you track dependencies using "COMPILE,", you will see that BAR is used four times in BLA and one
time in BLUB.
You just have to break the cycle, in the
present case by explicitly performing the old action of NAME>COMPILE
instead of just calling NAME>COMPILE.
Does SET-COMPSEM erase all memory of the original compilation semantics,
or is the old xt-comp still available? If not, we should be able to fix
this.
Ok, I see that there is a problem with using NAME>COMPILE within the
the closure's xt, passed to SET-COMPSEM.
NAME>COMPILE should be used outside the closure to obtain the
compilation semantics at the time that TRACK-DEPS was invoked. Thus a
permanent data structure, 3 cells long, may be passed into the closure.
The data structure contains the nt of the word, and the original
compilation semantics, which consists of two cells of data: x xt. Then
the closure can invoke both the original compilation semantics and
append the extra actions needed to add information to the dependency table. >>
Here's my new version of TRACK-DEPS. It invokes NAME>COMPILE outside of
the closure to obtain and store the current compilation semantics for
the word before SET-COMPSEM is invoked. It should satisfy all cases,
including preserving any extensions to the compilation semantics prior
to invoking TRACK-DEPS. I've tested it on the single-xt+immediate flag
cases. So it appears that the previous xt-comp is still valid.
: track-deps ( -- )
latestnt dup track-dependency ( -- nt-track)
dup name>compile ( -- nt-track x xt )
3 cells allocate drop \ reserve memory for old compsem
dup >r 2! r@ 2 cells + ! r>
[n:d dup 2@ execute 2 cells + a@ latestnt add-dependency ;]
set-compsem ;
Another way to do that is to simply pass the three values to the
closure. Gforth does not provide pure-stack closure construction
words for that, but unless you are a locals-hater, you may prefer the following (untested):
: track-deps ( -- )
latestnt dup track-dependency ( nt-track)
dup name>compile ( nt-track x xt )
[{: nt-track x xt :}d x xt execute nt-track latestnt add-dependency ;]
set-compsem ;
... What I should have said is that
compilation of a word should not be done with '(tick) and "COMPILE," but start with the name token, e.g. S" <name>" FIND-NAME and then use NAME>COMPILE and EXECUTE to append the appropriate semantics.
A concrete illustration in Gforth .....
require set-compsem.fs
I. Compilation semantics for an "ordinary word".
: foo ." foo" ;
s" foo" find-name name>compile .s
Thus, we see that from FOO's nt, NAME>COMPILE returns on the stack
nt -- xt-foo xt-compile,
II. Compilation semantics for an IMMEDIATE word..
: foo ." foo" ; IMMEDIATE
s" foo" find-name name>compile .s
From FOO's nt, NAME>COMPILE returns on the stack
nt -- xt-foo xt-execute
III. Compilation semantics for a dual-semantics word..
: foo ." foo" ; \ define word with default compilation semantics
compsem: 'f' emit 'o' emit 'o' emit ;
This example is neither case I nor case II.
s" foo" find-name name>compile .s
From FOO's nt, NAME>COMPILE returns on the stack
nt -- xt-? xt-execute
xt-? represents the compilation semantics of FOO and does not involve
xt-foo. ...
s" <name>" FIND-NAME NAME>COMPILE EXECUTE
will perform the specified compilation semantics for all cases. Cases I
and II apply to single-xt + immediate flag Forth systems, while Cases I
-- III apply to dual-xt systems.
A word definition may be immediately followed by the word TRACK-DEPS
which will set its compilation semantics to not only compile its interpretation semantics
On 2022-10-10 15:44, Krishna Myneni wrote:
A word definition may be immediately followed by the word TRACK-DEPS
which will set its compilation semantics to not only compile its
interpretation semantics
By this wording you just skew the language of the standard.
By default, not the interpretation semantics, but the execution
semantics of a word are appended (compiled):
| 3.4.3.3 Compilation semantics
| Unless otherwise specified in a "Compilation:" section
| of the glossary entry, the compilation semantics of a Forth
| definition shall be to append its *execution semantics*
| to the execution semantics of the current definition.
(emphasized by me)
There is a number of cases when interpretation semantics for a word are undefined by the standard, but execution semantics are defined.
But when interpretation semantics are defined via execution semantics,
it's still incorrect to say that they are equivalent in the general case.
Otherwise you introduce such a beast as interpretation semantics in compilation state — i.e., a behavior that takes place when the Forth
text interpreter encounters a word name in interpretation state, if the
Forth text interpreter in compilation state.
On 10/19/22 04:16, Ruvim wrote:
By default, not the interpretation semantics, but the execution
semantics of a word are appended (compiled):
| 3.4.3.3 Compilation semantics
| Unless otherwise specified in a "Compilation:" section
| of the glossary entry, the compilation semantics of a Forth
| definition shall be to append its *execution semantics*
| to the execution semantics of the current definition.
(emphasized by me)
The wording of the standard reflects a strict adherence to the single-xt model,
while we are discussing here a dual-xt model, in which the term
"execution semantics" can be omitted. In a single-xt system the
compilation semantics and the interpretation semantics are strongly connected,
while in a dual-xt system they are free to be specified separately.
In the ideal implementation of a dual-xt system, every word provides execution tokens for both interpretation semantics and compilation
semantics. When the text interpreter parses the word name it will
determine whether to perform the interpretation semantics or the
compilation semantics based on STATE. There is no need to talk of
"execution semantics".
For :NONAME definitions, quotations, and other
compositions which result in an execution token, we may simply speak of
the semantics resulting from applying EXECUTE to xt.
With the existence of dual-semantics Forth systems which are able to
properly run code written for single-xt systems, I would argue that the standard's definition of "compilation semantics", given in 3.4.3.3 is becoming obsolete.
There is a number of cases when interpretation semantics for a word
are undefined by the standard, but execution semantics are defined.
In a dual-semantics system, let's say you want to declare a word which
is not allowed to be used in interpretation state. We may do so, as
follows (in Gforth):
: NAME -14 throw ;
\ -14 is throw code for interpreting a compile-only word
COMPSEM: ( i*x -- j*x ) ... ;
Obviously this word has both interpretation semantics (throw an error)
and compilation semantics.
On 2022-10-19 22:33, Krishna Myneni wrote:
On 10/19/22 04:16, Ruvim wrote:
[...]
while we are discussing here a dual-xt model, in which the term
"execution semantics" can be omitted. In a single-xt system the
compilation semantics and the interpretation semantics are strongly
connected, while in a dual-xt system they are free to be specified
separately.
You are wrong: in a single-xt system they can be specified separately too.
: compilation ( -- flag ) state @ 0<> ;
: dual` ( xt.int xt.comp "name" -- )
2>r : 2r> 2lit,
[: ( i*x xt.int xt.comp -- j*x )
compilation if nip else drop then execute
;] compile, postpone ; immediate
;
:noname ( -- ) ." foo" ;
:noname ( -- ) ." bar" ;
dual` foo
Where xt.int and xt.comp are execution tokens for definitions that are
*used* to perform the interpretation semantics and compilation semantics
for /name/.
They don't identify the corresponding behaviors in the general case.
(it's true for dual-xt systems too).
In the ideal implementation of a dual-xt system, every word provides
execution tokens for both interpretation semantics and compilation
semantics. When the text interpreter parses the word name it will
determine whether to perform the interpretation semantics or the
compilation semantics based on STATE. There is no need to talk of
"execution semantics".
I totally disagree.
For :NONAME definitions, quotations, and other compositions which
result in an execution token, we may simply speak of the semantics
resulting from applying EXECUTE to xt.
These semantics are included into the notion of "execution semantics".
There is no any profit to use the phrase:
"the semantics resulting from applying EXECUTE to xt of a definition" instead of the phrase:
"the execution semantics of a definition"
With the existence of dual-semantics Forth systems which are able to
properly run code written for single-xt systems, I would argue that the
standard's definition of "compilation semantics", given in 3.4.3.3 is
becoming obsolete.
It makes standard systems non standard.
There is a number of cases when interpretation semantics for a word
are undefined by the standard, but execution semantics are defined.
In a dual-semantics system, let's say you want to declare a word which
is not allowed to be used in interpretation state. We may do so, as
follows (in Gforth):
: NAME -14 throw ;
\ -14 is throw code for interpreting a compile-only word
COMPSEM: ( i*x -- j*x ) ... ;
Obviously this word has both interpretation semantics (throw an error)
and compilation semantics.
Your example is not a case when interpretation semantics for a word are undefined by the standard, and execution semantics are defined.
In article <tiptve$4kuu$2@dont-email.me>,
Krishna Myneni <krishna.myneni@ccreweb.org> wrote:
<SNIP>
In a dual-semantics system, let's say you want to declare a word which
is not allowed to be used in interpretation state. We may do so, as
follows (in Gforth):
: NAME -14 throw ;
\ -14 is throw code for interpreting a compile-only word
COMPSEM: ( i*x -- j*x ) ... ;
Obviously this word has both interpretation semantics (throw an error)
and compilation semantics.
You contradict yourself. You stated that the word is not allowed to be
used in interpretation state.
So it cannot be even used to throw an error. 1)
(Language precision, important in this context, "this word cannot
be used in a valid ISO94 program." is correct.)
<SNIP>
In a dual-semantics system, let's say you want to declare a word which
is not allowed to be used in interpretation state. We may do so, as
follows (in Gforth):
: NAME -14 throw ;
\ -14 is throw code for interpreting a compile-only word
COMPSEM: ( i*x -- j*x ) ... ;
Obviously this word has both interpretation semantics (throw an error)
and compilation semantics.
--
Krishna
On 10/20/22 03:18, Ruvim wrote:
On 2022-10-19 22:33, Krishna Myneni wrote:
while in a dual-xt system they are free to be specified
separately.
You are wrong: in a single-xt system they can be specified separately
too.
: compilation ( -- flag ) state @ 0<> ;
: dual` ( xt.int xt.comp "name" -- )
2>r : 2r> 2lit,
[: ( i*x xt.int xt.comp -- j*x )
compilation if nip else drop then execute
;] compile, postpone ; immediate
;
:noname ( -- ) ." foo" ;
:noname ( -- ) ." bar" ;
dual` foo
Where xt.int and xt.comp are execution tokens for definitions that are
*used* to perform the interpretation semantics and compilation
semantics for /name/.
It's clever, but its state dependency means that FOO is no longer a "first-class" word in Forth.
For example, if we want to execute xt.comp
from the interpreter (the action of which is not ambiguous),
s" foo" find-name name>compile execute \ prints "foo"
which gives an incorrect result with your definition (it prints "foo").
FIND-NAME and NAME>COMPILE are standard words. You have created a
state-smart word which no longer behaves as expected for standard
operations. In a true dual-semantics system, the above sequence should
print "bar", e.g. in Gforth,
: foo ." foo" ; ok
compsem: ." bar" ; ok
s" foo" find-name name>compile execute \ prints "bar"
On 2022-10-20 11:41, Krishna Myneni wrote:
On 10/20/22 03:18, Ruvim wrote:
On 2022-10-19 22:33, Krishna Myneni wrote:
[...]
while in a dual-xt system they are free to be specified separately.
You are wrong: in a single-xt system they can be specified separately
too.
: compilation ( -- flag ) state @ 0<> ;
: dual` ( xt.int xt.comp "name" -- )
2>r : 2r> 2lit,
[: ( i*x xt.int xt.comp -- j*x )
compilation if nip else drop then execute
;] compile, postpone ; immediate
;
:noname ( -- ) ." foo" ;
:noname ( -- ) ." bar" ;
dual` foo
Where xt.int and xt.comp are execution tokens for definitions that
are *used* to perform the interpretation semantics and compilation
semantics for /name/.
It's clever, but its state dependency means that FOO is no longer a
"first-class" word in Forth.
What is a first-class word?
For example, if we want to execute xt.comp from the interpreter (the
action of which is not ambiguous),
s" foo" find-name name>compile execute \ prints "foo"
which gives an incorrect result with your definition (it prints "foo").
How do you know?
If a single-xt system provides a correct "name>compile", as:
: name>compile ( nt -- x xt )
name> ['] execute-compilatively
;
your test prints "bar".
FIND-NAME and NAME>COMPILE are standard words. You have created a
state-smart word which no longer behaves as expected for standard
operations. In a true dual-semantics system, the above sequence should
print "bar", e.g. in Gforth,
: foo ." foo" ; ok
compsem: ." bar" ; ok
s" foo" find-name name>compile execute \ prints "bar"
So, it is the same behavior that the system demonstrates when it
encounters the word name "foo" in compilation state.
On 10/20/22 15:25, Ruvim wrote:[...]
I've demonstrated that your DUAL` does not work with
NAME>COMPILE on a standard system.
And it's an expected behavior for your test, regardless how a word "foo"
is defined. Isn't it?
On 2022-10-20 22:01, Krishna Myneni wrote:
On 10/20/22 15:25, Ruvim wrote:[...]
I've demonstrated that your DUAL` does not work with NAME>COMPILE on a
standard system.
I cannot agree. There should be admitted either an ambiguous condition,
or an incompletely standard system.
But, before I try to convince you, could you please answer to my
question that you missed:
And it's an expected behavior for your test, regardless how a word "foo" >>> is defined. Isn't it?
Regardless how the word "foo" is defined, should your test demonstrate
the same behavior that the system demonstrates when the Forth text interpreter encounters the word name "foo" in compilation state?
Also, you may want to apply my test of your word DUAL` in other standard systems to see what they do.
On 10/14/22 02:21, Anton Ertl wrote:
Krishna Myneni <krishna.myneni@ccreweb.org> writes:
On 10/13/22 10:30, Anton Ertl wrote:
If SET-OPTIMIZER would not change COMPILE,, it would not work (it
would fail to set the optimizer). For Gforth's current
implementation, if SET-COMPSEM did not change NAME>COMPILE, it would
not work (it would fail to change the compilation semantics).
In the case of standard "COMPILE," the problem is that on a dual-xt
system, "COMPILE," operating on an xt is a fundamentally incorrect way
of compiling a named definition (word).
If you mean that, e.g.,
: [compile,] compile, ; immediate
: foo [ ' s" ] [compile,] bla" ;
is not (and must not be) equivalent to
: foo s" bla" ;
i.e., performing the compilation semantics of S", you are correct.
It's also incorrect for performing the compilation semantics of
: bar ." bar" ; immediate
But
: foo1 [ ' bar ] [compile,] ;
appends the execution=interpretation=compilation semantics to foo1, so
it is equivalent to
: foo1 postpone bar ;
Ticking named words is unambiguous for most named words, and
COMPILE,ing the resulting xt is just as standard as EXECUTEing it.
The above is only true when you are not talking about dual-semantics
words, where the compilation semantics is not simply that of "ordinary
words" or of immediate words.
Dual-semantics systems permit extended
compilation semantics. I think your term for such words is "combined
words". "COMPILE," is inadequate for combined words.
I understand
your "hack" in Gforth is for the purpose of maintaining backwards
compatibility.
I don't know what you mean with '"hack"'. Forth has COMPILE, for
appending semantics to the current definition, and NAME>COMPILE for
getting the compilation semantics of a named word. These are two
different jobs, and Gforth provides SET-OPTIMIZER and SET->COMP,
respectively, to provide implementations for these words for the
current definition. And that's the right way to do it.
I haven't implemented a dual-semantics system, so I won't strongly opine
on this other than to say, at the present time it does not seem to me
the right way to do it. "COMPILE," when used with NAME>INTERPRET, or >equivalently with '(tick) is fine when it is desired to append the >interpretation semantics, but for appending the compilation semantics of
a "combined word", one should use NAME>COMPILE and EXECUTE.
Because one often follows the other, there have been attempts to use
one mechanism for both purposes:
* It is incorrect to use SET-OPTIMIZER for changing the compilation
semantics.
* If you use SET->COMP for changing the code generation, you don't
cover the cases that COMPILE, xts directly instead of going through
NAME>COMPILE, and you have to provide a fallback mechanism for
COMPILE,ing that; and if you implement [COMPILE], you also have to
tell it which uses of SET->COMP change the default compilation
semantics and which don't. You may think that you are saving
complexity by eliminating SET-OPTIMIZER, but the complexity pops up
elsewhere.
I think what I'm advocating is that, in a dual-semantics system, the >programmer should be aware of when to use "COMPILE," and when not to use
it -- namely use it only for those definitions for which a name token is >meaningless,
and for words (named definitions) always use a compilation
chain which starts with the name token.
When I use "COMPILE,"deliberately I don't want the Forth system to alter the xt I give to >"COMPILE," behind the scenes due to the presence of arbitrary
compilation semantics.
I think that providing both mechanisms is a good solution, because it
also provides a nice separation of concerns: If you want to change the
generated code without changing the semantics (i.e., if you want to
optimize), use SET-OPTIMIZER; if you want to change the compilation
semantics, use SET->COMP (or, by extension, SET-COMPSEM).
Optimization is simply one rationale for changing the compilation
semantics.
I don't feel strongly about SET-OPTIMIZER as a substitute for
SET-COMPSEM.
What concerns me is that it modifies the behavior of
"COMPILE,". That seems to me to be unnecessary.
What I should have said is that
compilation of a word should not be done with '(tick) and "COMPILE," but >start with the name token, e.g. S" <name>" FIND-NAME and then use >NAME>COMPILE and EXECUTE to append the appropriate semantics.
It is further worth noting that on an *ideal* dual-xt system, as opposed
to a system which allows for dual-semantics, NAME>COMPILE would simply
have the following stack diagram for all three cases:
nt -- xt-comp xt-execute \ xt-comp represents the compilation semantics
Then the xt-execute may be dispensed with and NAME>INTERPRET and some >differently named NAME>COMPILE both would simply return a single xt. In
the *ideal* dual-xt system, we should be able to define
: NAME>COMPSEM ( nt -- xt-comp ) NAME>COMPILE DROP ;
EXECUTE(ing) xt-comp will perform the compilation semantics. This is not
how Gforth works at present (see case I). But, in my opinion, it would
be a cleaner and more consistent design, and easier to understand for
the programmer.
Krishna Myneni <krishna.myneni@ccreweb.org> writes:
What I should have said is that
compilation of a word should not be done with '(tick) and "COMPILE," but
start with the name token, e.g. S" <name>" FIND-NAME and then use
NAME>COMPILE and EXECUTE to append the appropriate semantics.
These are two different operations in general:
: foo [ ' <word> compile, ] ;
makes FOO perform the interpretation semantics of <word>.
On 10/29/22 08:41, Anton Ertl wrote:
Krishna Myneni <krishna.myneni@ccreweb.org> writes:
What I should have said is that
compilation of a word should not be done with '(tick) and "COMPILE," but >>> start with the name token, e.g. S" <name>" FIND-NAME and then use
NAME>COMPILE and EXECUTE to append the appropriate semantics.
These are two different operations in general:
: foo [ ' <word> compile, ] ;
makes FOO perform the interpretation semantics of <word>.
It is not true that FOO will perform the interpretation semantics of
<word> in Gforth if SET-OPTIMIZER has been used to change the
implementation of its compilation semantics. This is exactly my
objection to SET-OPTIMIZER -- it changes what COMPILE, does.
On 2022-10-29 16:10, Krishna Myneni wrote:
On 10/29/22 08:41, Anton Ertl wrote:
Krishna Myneni <krishna.myneni@ccreweb.org> writes:
What I should have said is that
compilation of a word should not be done with '(tick) and "COMPILE,"
but
start with the name token, e.g. S" <name>" FIND-NAME and then use
NAME>COMPILE and EXECUTE to append the appropriate semantics.
These are two different operations in general:
: foo [ ' <word> compile, ] ;
makes FOO perform the interpretation semantics of <word>.
It is not true that FOO will perform the interpretation semantics of
<word> in Gforth if SET-OPTIMIZER has been used to change the
implementation of its compilation semantics. This is exactly my
objection to SET-OPTIMIZER -- it changes what COMPILE, does.
It's a weak argument, since it can be easily eliminated by a proper specification for "set-optimizer" (or a similar word).
...
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 300 |
Nodes: | 16 (2 / 14) |
Uptime: | 51:47:43 |
Calls: | 6,712 |
Calls today: | 5 |
Files: | 12,243 |
Messages: | 5,355,044 |
Posted today: | 1 |