Search code examples
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:

  • "->" parse the two following names
  • for the first name create a dictionary entry "vehicle" for a 2variable like structure, if it doesn't already exists, a string will be assigned to it
  • for the second name create another entry "cars<" (note the <), if it doesn't already exists, also a 2variable
  • then add (not replace) the string "cars" to variable vehicle
  • and add the string "vehicles" to variable cars<

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, ( d-txt -- ) here swap dup allot move ;
    : s+ ( d-txt1 d-txt2 -- d-txt3 ) here >r 2swap s, s, r> here over - 0 c, ;
    : s+! ( d-txt addr -- ) dup >r 2@ 2swap s+ r> 2! ; \ '+!' naming convention 
    : gs+ ( d-txt1 -- d-txt2 ) s"  " s+ ; \ add gap string ('space+' is too long)
    \ some Forth-systems have these words as factors:
    : created ( d-txt-name -- ) s" create " 2swap s+ evaluate ;
    : obey ( i*x d-txt-name wid -- j*x true | i*x d-txt-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 ( d-txt-name -- addr )
      get-current >r v set-current
      created here 0 , 0 ,
      r> set-current
    ;
    : obtain-var ( d-txt-name -- addr )
      v obey if exit then  make-var
    ;
    : update-var ( d-txt-content d-txt-name -- )
      obtain-var s+!
    ;
    : -> \ "vehicles" "cars"
      parse-name parse-name
      2over gs+ 2over s" <" s+ update-var
      gs+ 2swap update-var
    ;
    

    d- prefix in the stack notation symbols here means a two-cells value, and d-txt- prefix means a string (cell-pair).

    s, ( d-txt -- ) stores given string into the data space as is (see also , and c,); NB: in Gforth it stores in counted string format (see 3.1.3.4 Counted strings).

    created is a postfix variant of create (in a proper system the last should be defined via the first); regarding etymology see also the standard words included (postfix form) and include (prefix form, parsing word).

    Also it is better to use a separate wordlist for these auto-created variables to avoid possible issues on clash 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 ./test/ directory). In this library -> word is defined for its own purpose, so the synonyms can be used to overcome undesired shadowing.