forthgforth

Syntactic sugar for making new dictionary entries + other operations in Gforth


I am a Forth newbie, trying to develop some (pseudo useful) toys to learn the language. I want to make the following operations condensed:

[ifundef] vehicles    2variable vehicles [then]
[ifundef] cars<       2variable cars<    [then]
vehicles 2@ s" cars "    s+ vehicles 2!
cars<    2@ s" vehicles" s+ cars<    2!

by the following (much more compact) instruction

-> vehicles cars

Or, in other words:

I made an hack to obtain just this behavior using strings manipulation and evaluating...

: space+ ( str -- ) s"  " s+ ;
\ use like: cars add alfa-romeo (first is a 2variable name, second a parsed name)
: add ( a "name" -- ) dup >r 2@ parse-name space+ s+ r> 2! ;

create _x 256 chars allot align
: _x@ ( -- ) _x count ;
: _x! ( -- ) _x place ;

create _y 256 chars allot align
: _y@ ( -- ) _y count ;
: _y! ( -- ) _y place ;

: init_x ( str -- ) 2dup s" [ifundef] " 2swap s+ s"  2variable " s+ 2swap s+ s"  [then]" s+ evaluate ;
: init_y ( str -- ) 2dup s" [ifundef] " 2swap s+ s" < 2variable " s+ 2swap s" <" s+ s+ s"  [then]" s+ evaluate ;

: make-dictionary-entries ( -- ) _x@ init_x    _y@ init_y ;
: add-strings-to-entries  ( -- ) _x@ s"  add "  s+ _y@ s+ evaluate
                                 _y@ s" < add " s+ _x@ s+ evaluate ;

: -> parse-name _x! parse-name _y!
     make-dictionary-entries
     add-strings-to-entries ;



\ CUSTOM TESTING to improve readability of the examples
: test( POSTPONE assert( ; immediate
: true! 0= throw ;
: false! throw ;
: same-string! str= true! ;

-> vehicles cars
test( vehicles 2@  s" cars "     same-string! )
test( cars< 2@     s" vehicles " same-string! )

-> vehicles trucks
-> vehicles dreams
test( vehicles 2@  s" cars trucks dreams " same-string! )
test( trucks< 2@   s" vehicles "           same-string! )

-> cars ferrari
-> cars lamborghini
-> dreams lamborghini
test( cars 2@          s" ferrari lamborghini " same-string! )
test( lamborghini< 2@  s" cars dreams "         same-string! )

I think another more direct, more elegant way exists, but this is the best I can do at this time. Any suggestions?


Solution

  • Some pieces of advice

    1. Start top-down decomposition from a pure postfix solution.
    2. Find some kind of ideal conceptual implementation of the top-level postfix solution.
    3. Implement the absent words and-or levels of your Forth-system.

    NB: It is not the general rules, but some weak points from the code in the question.

    One common rule is following. In the plumbing part the postfix syntax should be only used. The prefix syntax (parsing words) may take place as sugar on the top level only. I.e. any parsing word should have a postfix variant.

    A general postfix solution for the given problem is: s" content" s" name" update-var

    The solution

    \ reference implementations of some underlying words for testing purpose only
    : s, ( sd.data -- ) here swap dup allot move ;
    : s+ ( sd1 sd2 -- sd3 ) here >r 2swap s, s, r> here over - 0 c, ;
    : s+! ( sd addr -- ) dup >r 2@ 2swap s+ r> 2! ; \ '+!' naming convention 
    : gs+ ( sd1 -- sd2 ) s"  " s+ ; \ add gap string ('space+' is too long)
    \ some Forth-systems have these words as factors:
    : created ( sd.name -- ) s" create " 2swap s+ evaluate ;
    : obey ( i*x sd.name wid -- j*x true | i*x sd.name false )
      >r 2dup r> search-wordlist if nip nip execute true exit then  false
    ;
    
    \ the solution itself
    
    wordlist constant v \ for special auto-created variables
    
    : make-var ( sd.name -- addr )
      get-current >r v set-current
      created here 0 , 0 ,
      r> set-current
    ;
    : obtain-var ( sd.name -- addr )
      v obey if exit then  make-var
    ;
    : update-var ( sd.content sd.name -- )
      obtain-var s+!
    ;
    : -> \ "vehicles" "cars"
      parse-name parse-name
      2over gs+ 2over s" <" s+ update-var
      gs+ 2swap update-var
    ;
    

    The data type symbol sd stands for ( c-addr u ) cell-pair that represents a character string.

    s, ( sd -- ) stores given string into the data space as is (see also , and c,); NB: in Gforth a word with the same name stores a string in counted string format (see 3.1.3.4 Counted strings).

    created is a postfix variant of create (in a well designed system the latter should be defined via the former); for etymology see also the standard words included (a postfix form) and include (a prefix form, parsing word).

    It is also better to use a separate wordlist for these auto-created variables to avoid possible issues with clashing names (e.g. what if you need -> here str). See the variant without a separate wordlist in the revision 8.

    To write testcases the well-known ttester.fs library can be also used (in Gforth it is located in the test/ directory). In this library, the -> word is defined for its own purpose, so the synonyms can be used to overcome undesired shadowing.