Skip to content

Instantly share code, notes, and snippets.

@nfunato
Last active August 31, 2021 09:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nfunato/4641275 to your computer and use it in GitHub Desktop.
Save nfunato/4641275 to your computer and use it in GitHub Desktop.
The "Retro" Tofu Shop Game in Iscandar (in ANS Forth)

Revision History – the “Retro” tofu shop game in Iscandar (in ANS Forth)

このページは イスカンダルのトーフ屋ゲーム からリンクされています。

2020-11-15

  • Thinking Forth を (初めて)通読して、痛く反省したので refactoring してみた (tofu.fthのみ)
  • ファイル構成等の詳細は、tofu.fth の heading comment を参照 (なお、旧版(2013,2019)のgist filesは 目の毒なので、このgist entryの表向きからは消した)
  • TFを読んで思ったこと/悟ったことは以下

    – Forthプログラムでは stack jugglingするよりは、(グローバル)変数を使った方が良い場合もある。

    – stack effectにおいて、以前の版では 専らcaller saveになるようにしていたが、必ずしもそうでなくて良いようだ。 特に、上位の呼び出し階層において、ワード呼び出しの直前直後で argument save や stack jugglingだけのための ワードが現れない素直な point free styleになることを優先するのが良さげである。 (ただまあ何らかの法則はあった方が良いのかもしれない)

    – ワード .” を再定義した。これは初版(2013年)でも考えたのだが、non-portableになる気がしてやってなかった。 ところが、TFに「再定義推奨なんだけど、FIG83だとstate dependentの絡みで処理系依存になるよ」みたいなことが書いてある。 で、gforthのソースを読んでみたところ、現代でも再定義はnon-portableになるのが避けがたいように思えたので、それならと再定義してみた。 (しかし、gforthがnon-portableな書き方をしているのは、できないからという理由ではないかもしれず、上記の観測は誤っているかもしれない)

    で .” の定義は gforth依存ということになるが、 .” は(ワードtypeなんかと違って)コロン定義相当だろうし、依存しているのは 定義の内容というよりは それを処理系に伝える部分なので、他の処理系でも何か書く方法はある筈だと思う。

    – 少なくとも上記3点の効果で、以前よりはずいぶん読み易くなったのではないか。 標準FORTHの基本ワード(http://lars.nocrew.org/dpans/dpansf.htm )を識別できるくらいの人なら十分読めそうに思う。

    ちなみにFORTHの定義はボトムアップにしか配置できないので、大意を把握するには 下から上に向かって読む。

    – 一方で、手を抜いてあってすら 表示ルーチン(.xxxのような名前の奴ね)の定義ばっかりになってしまったような感じがする。 Common Lispならば format関数一発のような奴を長々と書くのは辛い…

    自分はFORTHという言語の面白さ(1970年代にOSすら無いようなbarebone machine上で、とある方向性においては 多少の先進性を得られる感じ)を味わうこともできるのだが、これに共感できるのは 当時の不便さを体感したオッサンくらいかもしれない。

2019-04-30

  • 何故か気が向いて、大幅に改版

    CLから安直に移植したことで、Forthとしては不自然だったところを修正した (特に、CLのcond式はForthに無いので、下手に表現するとifの入れ子が複数段になり、 stack jugglingが絡むと絶望的に読み難くなるように思う)

    長期休暇に逃避行動で書いていることが良く分かる…

  • ソースは tofu.fth とは別に lib.fth, anew.fth を登録

    libは一般的でライブラリになりそうなワードを分離したもの

    anewは同名の有名ユーティリティで、プリロードする必要がある

2013-01-26

  • お正月にお屠蘇飲みながら書いてみたForthへの移植を登録

    Forthは◯十年前に、1年半ほど楽しんで使っていたが、書くのはそれ以来かもしれない

    ANS仕様のForthは初めてで、仕様書を眺める時間が大半だった

  • ソースは tofu.fth のみ
\ -*- Mode: Forth -*-
\ Main program of the Tofu Shop Game in Iscandar
\
\ Copyright (C) 1978-2012 for the original version by Nobuhide Tsuda
\ See also http://vivi.dyndns.org/tofu/tofu.html for license
\
\ Copyright (C) 2013,2019,2020 for this Forth version by Nobuhiko Funato,
\ which is meant to be almost ANS94-compliant, and tested on gforth 0.7.0
\ and 0.7.3 (64bit word, Mac OSX).
\
\ Usage on gforth: $ gforth prelude.fth tofu.fth
\
\ Source files are prelude.fth, anew.fth, editor.fth, tofu_lib.fth,
\ and tofu.fth. Typically they can be placed in the same directories
\ for the usage above. The first three files are very short utilities,
\ and non-essential for this application.
\
\ prelude.fth just loads anew.fth and editor.fth
\ anew.fth gives Wil Baden's utility word ANEW (need to be preloaded)
\ editor.fth gives a word EDITOR which invokes an external editor vim
\ tofu_lib.fth gives general (and non-app-oriented) utility words
\ tofu.fth this file
\ ToDo (from more important to less):
\ - TWEAK GET-REAL-WEATHER TO REDUCE PROFIT MODERATELY
\ - make get-com-plan' wiser
\ Bugs
\ - a small value might have no presence in .forecast-in-bar and .cash-bar,
\ especially it tends to occur in the former, e.g.
\ sunny/cloudy/rainy 6%,92%,2% => no rainy part
ANEW --TOFU--
: this-file s" tofu.fth" ;
' this-file is editor-target
s" tofu_lib.fth" included \ quasi-general utilities
\ ===================================================================
\ local utilities
: under-swap ( x y z -- y x z )
>r swap r> ;
: tu~ck ( a b c -- c a b c )
-rot 2 pick ;
: two-map/1 ( x y f -- fx fy )
tuck 2>r execute 2r> execute ;
: two-map/2 ( x1 x2 y1 y2 f -- fx1x2 fy1y2 )
tu~ck >r 2>r execute 2r> r> execute ;
: two-map/3 ( x1 x2 x3 y1 y2 y3 f -- fx1x2x3 fy1y2y3 )
-rot 2>r tuck 2>r execute 2r> 2r> rot execute ;
: recover-1of3 ( x1 x2 total -- x1 x2 total-x1-x2 )
>r 2dup + r> swap - ;
: recover-1of2 ( x1 total -- x1 total-x1 )
over - ;
\ assume Bi ranges between 0 and 255 (so we need at-least 32bit word Forth)
: pack3b ( b3 b2 b1 -- packed-num ) 256 * + 256 * + ;
: unpack3b ( packed-num -- b3 b2 b1 ) 256 /mod 256 /mod ;
\ ===================================================================
\ Display utilities
variable 'ms-count
7 'ms-count !
: pauses ( u -- ) ?dup if 'ms-count @ * ms then ;
: pause 1 pauses ; \ pause one unit of time
\ slow-tty-mode
variable 'slow-tty?
'slow-tty? off
: pauses' ( u -- ) 'slow-tty? @ if pauses else drop then ;
: emit' ( x -- ) 'slow-tty? @ if pause then emit ;
: type' ( ca u -- ) ?dup if bounds do i c@ emit' loop else drop then ;
: .nchars ( n ch -- ) swap 0 ?do dup emit' loop drop ;
\ the following three lines defines
\ : ." ( "w" -- ) [char] " parse type' ;
\ in gforth dependent way
:noname '"' parse type' ;
:noname '"' parse postpone sliteral postpone type' ;
interpret/compile: ."
\ ===================================================================
\ Some explanations of the game
: show-credit ( -- )
cr
cr ." The Tofu Shop Game in Iscandar (ANS Forth Version)"
cr ." Copyright (C) 1978-2012 for the original version by Nobuhide Tsuda"
cr ." Copyright (C) 2013,2019,2020 for the Forth version by Nobuhiko Funato"
cr ;
: show-goal ( -- )
cr
cr ." Welcome to the planet ISCANDAR !"
cr ." You are the person who runs a tofu shop to make money for the"
cr ." cost of returning your mother planet, the EARTH."
cr ." (If you don't know well about ISCANDAR, you may want to see"
cr ." http://en.wikipedia.org/wiki/Space_Battleship_Yamato_planets .)"
cr ." But, on the other side of the street, there is the other shop run"
cr ." by a computer. The goal of the game is to compete against the "
cr ." computer for earning 30,000-yen sooner, necessary amount of money"
cr ." to the EARTH. The cash for each initially starts from 5,000-yen."
cr ." Here the cost price of a tofu is 40-yen, and the retail price of"
cr ." a tofu is 50-yen. Daily sales depend on the weather, i.e. "
cr ." upto 500 on sunny, upto 300 on cloudy, and upto 100 on rainy days."
cr ." Tofu spoils rapidly, so unsold tofus in a day must be thrown away."
cr ." Hence you should decide how many tofus you make for the next day, "
cr ." with close watching the weather forecast for tomorrow."
cr ;
\ ===================================================================
\ And the main logic of the game
\ Some predefined money amounts
40 constant $UNIT_COST_PRICE
50 constant $UNIT_RETAIL_PRICE
5000 constant $INITIAL_MAN_CASH
5000 constant $INITIAL_COM_CASH
30000 constant $SAVINGS_TARGET
: continue-game? ( cash -- bool )
\ underflow means bankruptcy, overflow means goal
1 $SAVINGS_TARGET within ;
: clip-cash-range ( cash -- cash' )
\ normalize cash, for cash<=0 to 0 and for $MAX<=cash to $MAX
\ (used in .CASH-BAR and COMPARE-CASH)
>r 0 $SAVINGS_TARGET r> clip-int ;
: max-tofus-to-make ( cash -- n )
\ make at least one tofu even insufficient budget, and then
\ go into bankrupt at the next settlement
$UNIT_COST_PRICE / 1 max ;
: calculate-profit/loss ( stock sales-limit -- pl )
over >r ( stock sales-limit ; r: stock )
min $UNIT_RETAIL_PRICE m* \ get real sales proceeds
r> $UNIT_COST_PRICE m* \ get sales cost, including unsold tofus
\ (note: unsold tofus will be abandoned)
d- d>s \ get profit or loss
;
\ -------------------------------------------------------------------
enum Weather
Weather SUNNY
Weather CLOUDY
Weather RAINY
begin-structure weather% \ Forth200x spec. (not Gforth's)
field: ->name
field: ->mood
field: ->sales-limit
end-structure
: weather-vec
create ( #elems -- ) weather% * allot
does> ( i -- aadr ) swap weather% * + ;
3 weather-vec wv
: ;; ( weather-idx name-cs mood-cs sales-limit -- )
3 roll wv >r
r@ ->sales-limit ! r@ ->mood ! r> ->name ! ;
:noname
\ idx name mood sales-limit
SUNNY c" sunny " c" \(^o^)/" 500 ;;
CLOUDY c" cloudy" c" (~_~) " 300 ;;
RAINY c" rainy " c" (;_;) " 100 ;;
; execute
: .weather-name ( weather -- ) wv ->name @ count type ;
: .weather-mood ( weather -- ) wv ->mood @ count type ;
: weather-sales-limit ( weather -- limit ) wv ->sales-limit @ ;
\ -------------------------------------------------------------------
: dot [char] . emit ;
: .weather ( weather -- )
dup
cr ." Today's weather is "
99 80 70 3 0 do pauses' dot space loop
85 pauses' .weather-name
65 pauses' .weather-mood
;
: get-real-weather' ( rPr/cPr/sPr -- weather )
\ remake sPr at random
unpack3b drop rand100 ( rPr cPr sPr' )
locals| sPr' cPr rPr |
sPr' rPr <= if RAINY exit then
sPr' rPr cPr + <= if CLOUDY exit then
SUNNY
;
: get-real-weather ( rPr/cPr/sPr -- weather )
get-real-weather' dup .weather ;
\ -------------------------------------------------------------------
40 constant WEATHER_BAR_WIDTH
: scale-xPr ( xPr -- scaled-xPr ) \ scale prob for bar
>r WEATHER_BAR_WIDTH 100 r> scaling ;
: scale-probs-for-bar ( rPr cPr sPr -- scaled-rPr scaled-cPr scaled-sPr )
drop \ regenerate sPr later as scaled-sPr
['] scale-xPr two-map/1
WEATHER_BAR_WIDTH recover-1of3 ;
\ need 'tweak' forecast-bar for the issue in Bugs ?
: .forecast-in-bar ( rPr/cPr/sPr -- )
unpack3b scale-probs-for-bar
cr 18 spaces [char] O .nchars [char] ^ .nchars [char] X .nchars ;
: .forecast-in-percent ( rPr/cPr/sPr -- )
unpack3b
cr ." Tomorrow weather: "
." Sunny " 2 u.r
." % , Cloudy " 2 u.r
." % , Rainy " 2 u.r
." %" ;
: .forecast ( rPr/cPr/sPr -- )
dup .forecast-in-percent .forecast-in-bar ;
: get-weather-forecast' ( -- rPr/cPr/sPr )
\ make Probabilities for Rainy, Sunny (that for Cloudy is implicit)
rand100 rand100 2>r
2r@ min \ rPr : (min pr1 pr2)
100 2r> max - \ sPr : (- 100 (max pr1 pr2))
\ ( rPr sPr )
\ add cPr
100 recover-1of3 swap
pack3b ;
: get-weather-forecast ( -- rPr/cPr/sPr )
get-weather-forecast' dup .forecast ;
\ -------------------------------------------------------------------
30 constant CASH_BAR_WIDTH
: scale-cash ( cash -- scaled-cash ) \ scale cash for bar
>r CASH_BAR_WIDTH $SAVINGS_TARGET r> scaling ;
: scaled-cash/nocash ( cash -- scaled-cash scaled-nocash )
scale-cash CASH_BAR_WIDTH recover-1of2 ;
: .cash-bar ( cash -- )
\ without the clipping, either of scaled amounts might be minus,
\ which will be intepreted as a huge unsigned number by .nchars.
clip-cash-range
scaled-cash/nocash swap
\ output '#' CASH-BAR and '-' NOCASH-BAR
[char] # .nchars [char] - .nchars ;
: .cash ( cash -- )
5 .r ." -yen " ;
: .wallet ( cash -- )
dup .cash .cash-bar ;
\ print the wallets somewhat visually
: .wallets ( man-cash com-cash -- )
swap
cr ." wallets: "
cr ." man: " .wallet
cr ." com: " .wallet
cr
;
\ -------------------------------------------------------------------
\ FIXME: introducing Bayesian estimation or such things someday :)
: get-com-plan' ( rPr/cPr/sPr -- com-plan )
unpack3b nip
locals| sPr rPr |
50 sPr <= if 500 exit then
30 rPr < if 100 exit then
300
;
: .com-plan ( com-plan -- )
cr ." Computer will make " . space ." tofus." ;
: get-com-plan ( cash rPr/cPr/sPr -- com-plan )
get-com-plan' >r max-tofus-to-make r> min
dup .com-plan ;
: get-man-plan ( cash _rPr/cPr/sPr -- man-plan )
drop max-tofus-to-make >r
c" How many tofus will you make?" 1 r> get-int-within-range ;
\ -------------------------------------------------------------------
: both-continue? ( cash1 cash2 -- bool )
['] continue-game? two-map/1 and ;
\ We can think of variables 'wf and 'rw as closure-variables,
\ which make our program clear -- although they make it non-reentrant.
variable 'wf \ weather-forecast
: get-plan ( cash flg -- stock )
'wf @ swap if get-man-plan else get-com-plan then ;
variable 'rw \ real-weather
: update-cash ( cash stock -- cash' )
'rw @ weather-sales-limit calculate-profit/loss + ;
variable 'day
: play-one-game ( man-cash com-cash -- man-cash' com-cash' )
randomize
1 'day !
begin
2dup .wallets
2dup both-continue? while
\ sell-tofus-for-one-day
2dup true false ( m c m c #t #f )
get-weather-forecast 'wf !
under-swap ['] get-plan two-map/2 ( m c m-stock c-stock )
cr cr ." * * * * * * Day " 'day @ 2 u.r ." * * * * * *"
'wf @ get-real-weather 'rw !
under-swap ['] update-cash two-map/2 ( m' c' )
1 'day +!
repeat ;
: compare-cash ( man-cash com-cash -- ordering )
\ clip args, since it's a draw when both go bankrupt or when both goal
['] clip-cash-range two-map/1 - sgn ;
: main ( -- )
show-credit
s" Do you want TheMatrix display mode?" y-or-n-p 'slow-tty? !
s" Do you want to read the rule of the game?" y-or-n-p if show-goal then
begin
$INITIAL_MAN_CASH $INITIAL_COM_CASH play-one-game
compare-cash case
LT of cr ." Computer win!" endof
EQ of cr ." It is a draw." endof
GT of cr ." You win!" endof
abort" main"
endcase
s" Play another game?" yes-or-no-p while
repeat ;
\ This file is meant to be used with tofu.fth, so see the heading comment in it.
\ Quasi general words spinned off from tofu.fth
\ ToDo
\ - introduce 64-bit XorShift PRNG into lib.fth
ANEW --TOFU_LIB--
\ export the following words to tofu.fth
\ 3dup ( x y z -- x y z )
\ 3drop ( x y z -- )
\ yes-or-no-p ( ca u -- f )
\ y-or-n-p ( ca u -- f )
\ enum ( "w" -- )
\ scaling ( scaled-all all n -- scaled-n )
\ clip-int ( min max i1 -- i2 )
\ enum Ordering { LT EQ GT }
\ compare-int ( a b -- ordering )
\ get-int-within-range ( prompt-cs min max -- n )
\ randomize ( -- )
\ rand100 ( -- 0..100 )
\ s" compat/assert.fs" included
\ 0 assert-level !
\ ===================================================================
\ general utilities
: not ( x -- flag ) s" 0= " evaluate ; immediate
\ : -rot ( x y z -- z x y ) rot rot ;
: 3dup ( x y z -- x y z x y z ) 2 pick 2 pick 2 pick ;
: 3drop ( x y z -- ) 2drop drop ;
: word-bits ( -- +n ) 1 cells 8 * ;
: swap- swap - ;
: between ( n1 n2 n3 -- f )
1+ within ;
\ already in gforth -------------------------------------------------
\ : th ( a-addr1 n -- a-addr2 ) cells + ;
\ note: cells == cell *
\ (cell is not in ANS, but constant in gforth )
: cell-vec
create ( #elems -- ) cells allot
does> ( i -- a-addr ) swap cells + ; \ swap th
\ examples
\ create vec1 1001 cells allot
\ true vec1 4 th !
\ 1001 array vec2
\ false 4 vec2 !
\ 4 vec2 @
\ 5 vec2 ?
\ : under+ ( a b c -- a+c b ) rot + swap ;
\ : on ( adr -- ) true swap ! ;
\ : off ( adr -- ) false swap ! ;
\ : bounds ( adr cnt -- adr+cnt adr ) over + swap ;
\ : sgn ( -n|0|+n -- -1|0|+1 ) dup if 0< 1 or then ; \ 'direction' in TF
\ note: gforth's sgn
\ : sgn ( -n|0|+n -- -1|0|+1 ) dup 0= if exit then 0< 2* 1+ ;
\ ===================================================================
\ enum
: ++ ( addr -- ) 1 swap +! ;
: enum-from ( n <word> -- ) create , does> dup @ constant ++ ;
: enum ( <word> -- ) 0 enum-from ;
\ ===================================================================
\ integer operations
: scaling ( scaled-all all n -- scaled-n )
-rot */ ;
: clip-int ( min max i1 -- i2 )
min max ;
-1 enum-from Ordering
Ordering LT \ -1
Ordering EQ \ 0
Ordering GT \ +1
\ : compare-int ( i1 i2 -- ordering ) - sgn ;
\ ===================================================================
\ yes-or-no-p, y-or-n-p
: clear-input begin key? while key drop repeat ;
: compare-cs ( ca u cs -- f ) count compare ;
: x-or-y-prompt ( cs-x cs-y ca u -- ) \ printf("\n%s (%s or %s) ", s, x, y)
cr type
2>r c" ) " r> c" or " r> c" ("
5 0 do count type loop ;
: x-or-y-p ( ca u cs-x cs-y -- f )
clear-input
locals| cs-y cs-x u ca |
begin
cs-x cs-y ca u x-or-y-prompt
pad dup 10 accept \ note: gforth's ACCEPT echo-backs input
( ca' u' -- ) \ an accepted string is on the stack
2dup
cs-x compare-cs 0= if 2drop true true else
cs-y compare-cs 0= if false true else
false then then
until ;
: y-or-n-p ( ca u -- f ) c" y" c" n" x-or-y-p ;
: yes-or-no-p ( ca u -- f ) c" yes" c" no" x-or-y-p ;
\ ===================================================================
\ Input utilities
: get-int ( -- n )
0. \ push UD ZERO onto stack
pad dup 6 accept \ ( 0. ca1 u1 )
>number \ ( ud2 ca2 u2 )
2drop d>s ;
: prompt-range ( cs min max --) \ printf("\n%s ( %d -- %d ) ", s, min, max)
rot count cr type swap
space [char] ( emit space . s" -- " type . [char] ) emit space ;
: get-int-within-range ( cs min max -- n )
0 begin
drop 3dup prompt-range \ ( cs min max )
get-int \ ( cs min max n )
3dup -rot \ ( cs min max n min the-max )
between \ ( cs min max bool )
until >r 3drop r> ; \ leave only bool
\ ===================================================================
\ slow-tty-mode
\ variable 'ms-count
\ 7 'ms-count !
\ : pauses ( u -- ) ?dup if 'ms-count @ * ms else drop then ;
\ : 1pause 1 pauses ;
\ \ slow-tty-mode
\ variable 'slow-tty?
\ 'slow-tty? off
\ : emit' ( x -- ) 'slow-tty? @ if 1pause then emit ;
\ : type' ( ca u -- ) ?dup if bounds do i c@ emit' loop else drop then ;
\ : type' ( ca u -- ) 0 ?do dup i + c@ emit' loop drop ;
\ \ gforth dependent
\ :noname '"' parse type' ;
\ :noname '"' parse postpone sliteral postpone type' ;
\ interpret/compile: ."
\ ===================================================================
\ PRNG
enum PrngType
\ PrngType PRNG_SIMPLE_LINEAR_CONGRUENTIAL
PrngType PRNG_GFORTH_BUILTIN
PrngType PRNG_C_G_MONTGOMERY
PrngType PRNG_XOR_SHIFT
\ choose one of aboves
\ PRNG_GFORTH_BUILTIN constant PRNG
PRNG_C_G_MONTGOMERY constant PRNG
\ PRNG_XOR_SHIFT constant PRNG
\ -------------------------------------------------------------------
PRNG PRNG_GFORTH_BUILTIN = [IF]
require random.fs
: randomize ( -- ) \ assuming CELL returns 8 (see random.fs)
time&date 2drop ( sec min hr day )
+ + + 0 ?do rnd drop loop
rnd seed ! ;
\ random ( n -- 0..n-1 )
[THEN]
\ -------------------------------------------------------------------
PRNG PRNG_C_G_MONTGOMERY = [IF]
\ From https://groups.google.com/forum/#!topic/comp.lang.forth/DglVTqncYzQ
\ See also https://groups.google.com/forum/#!topic/comp.lang.forth/4hOqv2m8wA4
\ especially, 2011-12-14(Hans Bezemer) and 2011-12-15(Brad Eckert)
1 cells 2 = [IF]
26088 ( 65E8 ) constant RMULT
[ELSE]
2051013963 ( 7A3FFD4B ) constant RMULT
[THEN]
2variable *rloc* 3 1 *rloc* 2!
: rnd ( -- u ) *rloc* 2@ RMULT um* rot 0 d+ over *rloc* 2! ;
: randomize ( -- ) \ reseed randomly by exerciseing rnd a few times
time&date 2drop ( sec min hr day )
+ + + 0 ?do rnd drop loop
rnd rnd *rloc* 2! ;
: random ( n -- 0..n-1 ) rnd um* nip ;
[THEN]
\ -------------------------------------------------------------------
PRNG PRNG_XOR_SHIFT = [IF]
\ FIXME
[THEN]
\ -------------------------------------------------------------------
\ And common utility PRNG words
: rand100 ( -- 0..100 )
101 random ;
\ in gforth, the definition of include is as follows
\ : include ( "name" -- ) bl-word count included ;
\ and the following lines are definitely portable.
s" anew.fth" included
s" editor.fth" included
\ from Wil Baden's "ToolBelt 2002"
: possibly ( "name" -- ) bl word find ?dup and if execute then ;
: anew ( "name" -- ) >in @ possibly >in ! marker ;
\ typical usage:
\ - put the following two lines in a editor target file, say "xxx.fth"
\ : this-file s" xxx.fth" ; \ "this-file" can be any other name
\ ' this-file is editor-target
\ - execute the word EDITOR after loading xxx.fth
defer editor-target ( -- c-addr u )
: set-editor-target ( xt -- ) is editor-target ;
: edcmd0 s" vim -S ~/home/vim/forth.vim " ;
: append' ( fr u2 to u1 -- to u1+u2 ) over >r tuck + -rot over + >r move 2r> ;
: append ( fr u2 to-cs -- ) count append' swap c! ; \ update to-cs len
: concat ( to u1 fr u2 -- to u1+u2 ) 2swap append' ;
: edcmd pad 0 edcmd0 concat editor-target concat ;
: ZZ editor-target included ; \ load editor target
: editor edcmd system ZZ ; \ do ZZ after quiting the editor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment