library TVM_Asm // simple TVM Assembler namespace Asm Asm definitions "0.4.5" constant asm-fif-version variable @atend variable @was-split false @was-split ! { "not in asm context" abort } @atend ! { `normal eq? not abort"must be terminated by }>" } : @normal? { context@ @atend @ 2 { @atend ! context! @normal? } does @atend ! } : @pushatend { @pushatend Asm { }> b> } : }>c { }>c s { @atend @ 2 { true @was-split ! @atend ! rot b> ref, swap @endblk } does @atend ! = -rot <= and } : 2x<= { 2 pick brembitrefs 1- 2x<= } : @havebitrefs { @havebits ' @| ifnot } : @ensurebits { @havebitrefs ' @| ifnot } : @ensurebitrefs { rot over @ensurebits -rot u, } : @simpleuop { tuck sbitrefs @ensurebitrefs swap s, } : @addop { tuck bbitrefs @ensurebitrefs swap b+ } : @addopb ' @addopb : @inline { 1 ' @addop does create } : @Defop { 1 { } : si() // x mi ma -- ? { rot tuck >= -rot <= and } : @range { rot tuck < -rot > or } : @-range { @-range abort"Out of range" } : @rangechk { dup 0 < over 255 > or abort"Invalid stack register number" si() } : s() { si() constant } : @Sreg -2 @Sreg s(-2) -1 @Sreg s(-1) 0 @Sreg s0 1 @Sreg s1 2 @Sreg s2 3 @Sreg s3 4 @Sreg s4 5 @Sreg s5 6 @Sreg s6 7 @Sreg s7 8 @Sreg s8 9 @Sreg s9 10 @Sreg s10 11 @Sreg s11 12 @Sreg s12 13 @Sreg s13 14 @Sreg s14 15 @Sreg s15 { dup 0 < over 7 > or abort"Invalid control register number" } : c() { c() constant } : @Creg 0 @Creg c0 1 @Creg c1 2 @Creg c2 3 @Creg c3 4 @Creg c4 5 @Creg c5 7 @Creg c7 { abort"not a stack register" 12 i@+ s> } : @bigsridx { @bigsridx dup 16 >= over 0< or abort"stack register s0..s15 expected" } : @sridx { rot @bigsridx tuck < -rot tuck > rot or abort"stack register out of range" } : @sridxrange { swap @bigsridx + dup 16 >= over 0< or abort"stack register out of range" } : @sridx+ { swap 0xcc <> over 7 > or over 6 = or abort"not a control register c0..c5 or c7" } : @cridx { = { tuck 16 >= { = and { 15 and abort"integer too large" 8 + 2dup fits } until > 2- 5 u, -rot i, } cond } cond } cond @addopb } dup : PUSHINT : INT { dup 256 = abort"use PUSHNAN instead of 256 PUSHPOW2" = or abort"invalid slice padding" swap 1 1 u, 0 rot u, } : @scomplete { tuck sbitrefs swap 26 + swap @havebitrefs not { PUSHREFSLICE } { over sbitrefs 2dup 123 0 2x<= { drop tuck 4 + 3 >> swap x{8B} s, over 4 u, 3 roll s, -rot 3 << 4 + swap - @scomplete } { 2dup 1 >= swap 248 <= and { rot x{8C} s, swap 1- 2 u, over 7 + 3 >> tuck 5 u, 3 roll s, -rot 3 << 1 + swap - @scomplete } { rot x{8D} s, swap 3 u, over 2 + 3 >> tuck 7 u, 3 roll s, -rot 3 << 6 + swap - @scomplete } cond } cond } cond } dup : PUSHSLICE : SLICE // ( b' -- ? ) { bbitrefs or 0= } : @cont-empty? { bbits 7 and 0= } : @cont-aligned? // ( b b' -- ? ) { bbitrefs over 7 and { 2drop drop false } { swap 16 + swap @havebitrefs nip } cond } : @cont-fits? // ( b b' -- ? ) { bbitrefs over 7 and { 2drop drop false } { 32 1 pair+ @havebitrefs nip } cond } : @cont-ref-fit? // ( b b' b'' -- ? ) { over @cont-aligned? over @cont-aligned? and not { 2drop drop false } { bbitrefs rot bbitrefs pair+ swap 32 + swap @havebitrefs nip } cond } : @two-cont-fit? { 2dup @cont-fits? not { b> PUSHREFCONT } { swap over bbitrefs 2dup 120 0 2x<= { drop swap x{9} s, swap 3 >> 4 u, swap b+ } { rot x{8F_} s, swap 2 u, swap 3 >> 7 u, swap b+ } cond } cond } dup : PUSHCONT : CONT { }> PUSHCONT } : }>CONT { { @normal? PUSHCONT } @doafter<{ } : CONT:<{ // arithmetic operations { 2 { rot dup 8 fits { nip = { rot drop -rot PUSHINT swap LSHIFT# } { { drop PUSHINT } { not pow2decomp swap -1 = { nip PUSHPOW2DEC } { drop PUSHINT } cond } cond } cond } cond } cond } cond } dup : PUSHINTX : INTX // integer comparison x{B8} @Defop SGN x{B9} @Defop LESS x{BA} @Defop EQUAL x{BB} @Defop LEQ x{BC} @Defop GREATER x{BD} @Defop NEQ x{BE} @Defop GEQ x{BF} @Defop CMP x{C0} x{BA} @Defop(8i,alt) EQINT x{C000} @Defop ISZERO x{C1} x{B9} @Defop(8i,alt) LESSINT { 1+ LESSINT } : LEQINT x{C100} @Defop ISNEG x{C101} @Defop ISNPOS x{C2} x{BC} @Defop(8i,alt) GTINT { 1- GTINT } : GEQINT x{C200} @Defop ISPOS x{C2FF} @Defop ISNNEG x{C3} x{BD} @Defop(8i,alt) NEQINT x{C300} @Defop ISNZERO x{C4} @Defop ISNAN x{C5} @Defop CHKNAN // other comparison x{C700} @Defop SEMPTY x{C701} @Defop SDEMPTY x{C702} @Defop SREMPTY x{C703} @Defop SDFIRST x{C704} @Defop SDLEXCMP x{C705} @Defop SDEQ x{C708} @Defop SDPFX x{C709} @Defop SDPFXREV x{C70A} @Defop SDPPFX x{C70B} @Defop SDPPFXREV x{C70C} @Defop SDSFX x{C70D} @Defop SDSFXREV x{C70E} @Defop SDPSFX x{C70F} @Defop SDPSFXREV x{C710} @Defop SDCNTLEAD0 x{C711} @Defop SDCNTLEAD1 x{C712} @Defop SDCNTTRAIL0 x{C713} @Defop SDCNTTRAIL1 // cell serialization (Builder manipulation primitives) x{C8} @Defop NEWC x{C9} @Defop ENDC x{CA} @Defop(8u+1) STI x{CB} @Defop(8u+1) STU x{CC} @Defop STREF x{CD} dup @Defop STBREFR @Defop ENDCST x{CE} @Defop STSLICE x{CF00} @Defop STIX x{CF01} @Defop STUX x{CF02} @Defop STIXR x{CF03} @Defop STUXR x{CF04} @Defop STIXQ x{CF05} @Defop STUXQ x{CF06} @Defop STIXRQ x{CF07} @Defop STUXRQ x{CF08} @Defop(8u+1) STI_l x{CF09} @Defop(8u+1) STU_l x{CF0A} @Defop(8u+1) STIR x{CF0B} @Defop(8u+1) STUR x{CF0C} @Defop(8u+1) STIQ x{CF0D} @Defop(8u+1) STUQ x{CF0E} @Defop(8u+1) STIRQ x{CF0F} @Defop(8u+1) STURQ x{CF10} @Defop STREF_l x{CF11} @Defop STBREF x{CF12} @Defop STSLICE_l x{CF13} @Defop STB x{CF14} @Defop STREFR x{CF15} @Defop STBREFR_l x{CF16} @Defop STSLICER x{CF17} dup @Defop STBR @Defop BCONCAT x{CF18} @Defop STREFQ x{CF19} @Defop STBREFQ x{CF1A} @Defop STSLICEQ x{CF1B} @Defop STBQ x{CF1C} @Defop STREFRQ x{CF1D} @Defop STBREFRQ x{CF1E} @Defop STSLICERQ x{CF1F} dup @Defop STBRQ @Defop BCONCATQ x{CF20} @Defop(ref) STREFCONST { > tuck 3 u, 3 roll s, -rot 3 << 2 + swap - @scomplete } { 2drop swap PUSHSLICE STSLICER } cond } cond } : STSLICECONST x{CF81} @Defop STZERO x{CF83} @Defop STONE // cell deserialization (CellSlice primitives) x{D0} @Defop CTOS x{D1} @Defop ENDS x{D2} @Defop(8u+1) LDI x{D3} @Defop(8u+1) LDU x{D4} @Defop LDREF x{D5} @Defop LDREFRTOS x{D6} @Defop(8u+1) LDSLICE x{D700} @Defop LDIX x{D701} @Defop LDUX x{D702} @Defop PLDIX x{D703} @Defop PLDUX x{D704} @Defop LDIXQ x{D705} @Defop LDUXQ x{D706} @Defop PLDIXQ x{D707} @Defop PLDUXQ x{D708} @Defop(8u+1) LDI_l x{D709} @Defop(8u+1) LDU_l x{D70A} @Defop(8u+1) PLDI x{D70B} @Defop(8u+1) PLDU x{D70C} @Defop(8u+1) LDIQ x{D70D} @Defop(8u+1) LDUQ x{D70E} @Defop(8u+1) PLDIQ x{D70F} @Defop(8u+1) PLDUQ { dup 31 and abort"argument must be a multiple of 32" 5 >> 1- > swap x{D72A_} s, over 7 u, 3 roll s, -rot 3 << 3 + swap - @scomplete } : SDBEGINS:imm { tuck sbitrefs abort"no references allowed in slice" dup 26 <= { drop > swap x{D72E_} s, over 7 u, 3 roll s, -rot 3 << 3 + swap - @scomplete } : SDBEGINSQ:imm { tuck sbitrefs abort"no references allowed in slice" dup 26 <= { drop rot 2 } { swap @| swap 2dup @cont-fits? { rot 1 } { b> rot 2 } cond } cond } cond } cond [] execute } : @run-cont-op { triple 1 ' @run-cont-op does create } : @def-cont-op { DROP } { PUSHCONT IF } { IFREF } @def-cont-op IF-cont { IFRET } { PUSHCONT IFJMP } { IFJMPREF } @def-cont-op IFJMP-cont { DROP } { PUSHCONT IFNOT } { IFNOTREF } @def-cont-op IFNOT-cont { IFNOTRET } { PUSHCONT IFNOTJMP } { IFNOTJMPREF } @def-cont-op IFNOTJMP-cont { dup 2over rot } : 3dup recursive IFELSE-cont2 { dup @cont-empty? { drop IF-cont } { over @cont-empty? { nip IFNOT-cont } { 3dup @two-cont-fit? { -rot PUSHCONT swap PUSHCONT IFELSE } { 3dup nip @cont-ref-fit? { rot swap PUSHCONT swap b> IFREFELSE } { 3dup drop @cont-ref-fit? { -rot PUSHCONT swap b> IFELSEREF } { rot 32 2 @havebitrefs { rot b> rot b> IFREFELSEREF } { @| -rot IFELSE-cont2 } cond } cond } cond } cond } cond } cond } swap ! { }> IF-cont } : }>IF { }> IFNOT-cont } : }>IFNOT { }> IFJMP-cont } : }>IFJMP { }> IFNOTJMP-cont } : }>IFNOTJMP { { @normal? IFJMP-cont } @doafter<{ } : IFJMP:<{ { { @normal? IFNOTJMP-cont } @doafter<{ } : IFNOTJMP:<{ { `else @endblk } : }>ELSE<{ { `else: @endblk } : }>ELSE: { 1 { swap @normal? swap IFELSE-cont2 } does @doafter<{ } : @doifelse { 1 { swap @normal? IFELSE-cont2 } does @doafter<{ } : @doifnotelse { { dup `else eq? { drop @doifelse } { dup `else: eq? { drop IFJMP-cont } { @normal? IF-cont } cond } cond } @doafter<{ } : IF:<{ { { dup `else eq? { drop @doifnotelse } { dup `else: eq? { drop IFNOTJMP-cont } { @normal? IFNOT-cont } cond } cond } @doafter<{ } : IFNOT:<{ x{E304} @Defop CONDSEL x{E305} @Defop CONDSELCHK x{E308} @Defop IFRETALT x{E309} @Defop IFNOTRETALT { DO<{ { `do: @endblk } : }>DO: { }> PUSHCONT REPEAT } : }>REPEAT { { @normal? PUSHCONT REPEAT } @doafter<{ } : REPEAT:<{ { }> PUSHCONT UNTIL } : }>UNTIL { { @normal? PUSHCONT UNTIL } @doafter<{ } : UNTIL:<{ { PUSHCONT { @normal? PUSHCONT WHILE } @doafter<{ } : @dowhile { { dup `do eq? { drop @dowhile } { `do: eq? not abort"`}>DO<{` expected" PUSHCONT WHILEEND } cond } @doafter<{ } : WHILE:<{ { }> PUSHCONT AGAIN } : }>AGAIN { { @normal? PUSHCONT AGAIN } @doafter<{ } : AGAIN:<{ x{E314} @Defop REPEATBRK x{E315} @Defop REPEATENDBRK x{E316} @Defop UNTILBRK x{E317} dup @Defop UNTILENDBRK @Defop UNTILBRK: x{E318} @Defop WHILEBRK x{E319} @Defop WHILEENDBRK x{E31A} @Defop AGAINBRK x{E31B} dup @Defop AGAINENDBRK @Defop AGAINBRK: { }> PUSHCONT REPEATBRK } : }>REPEATBRK { { @normal? PUSHCONT REPEATBRK } @doafter<{ } : REPEATBRK:<{ { }> PUSHCONT UNTILBRK } : }>UNTILBRK { { @normal? PUSHCONT UNTILBRK } @doafter<{ } : UNTILBRK:<{ { PUSHCONT { @normal? PUSHCONT WHILEBRK } @doafter<{ } : @dowhile { { dup `do eq? { drop @dowhile } { `do: eq? not abort"`}>DO<{` expected" PUSHCONT WHILEENDBRK } cond } @doafter<{ } : WHILEBRK:<{ { }> PUSHCONT AGAINBRK } : }>AGAINBRK { { @normal? PUSHCONT AGAINBRK } @doafter<{ } : AGAINBRK:<{ // // continuation stack manipulation and continuation creation // { PUSHCONT ATEXIT } : }>ATEXIT { { @normal? PUSHCONT ATEXIT } @doafter<{ } : ATEXIT:<{ x{EDF4} @Defop ATEXITALT { }> PUSHCONT ATEXITALT } : }>ATEXITALT { { @normal? PUSHCONT ATEXITALT } @doafter<{ } : ATEXITALT:<{ x{EDF5} @Defop SETEXITALT { }> PUSHCONT SETEXITALT } : }>SETEXITALT { { @normal? PUSHCONT SETEXITALT } @doafter<{ } : SETEXITALT:<{ x{EDF6} @Defop THENRET x{EDF7} @Defop THENRETALT x{EDF8} @Defop INVERT x{EDF9} @Defop BOOLEVAL x{EDFA} @Defop SAMEALT x{EDFB} @Defop SAMEALTSAVE // x{EE} is BLESSARGS // // dictionary subroutine call/jump primitives { c3 PUSH EXECUTE } : CALLVAR { c3 PUSH JMPX } : JMPVAR { c3 PUSH } : PREPAREVAR { dup 14 ufits { dup 8 ufits { CATCH<{ { PUSHCONT { @normal? PUSHCONT TRY } @doafter<{ } : @trycatch { { `catch eq? not abort"`}>CATCH<{` expected" @trycatch } @doafter<{ } : TRY:<{ // // dictionary manipulation ' NULL : NEWDICT ' ISNULL : DICTEMPTY ' STSLICE : STDICTS x{F400} dup @Defop STDICT @Defop STOPTREF x{F401} dup @Defop SKIPDICT @Defop SKIPOPTREF x{F402} @Defop LDDICTS x{F403} @Defop PLDDICTS x{F404} dup @Defop LDDICT @Defop LDOPTREF x{F405} dup @Defop PLDDICT @Defop PLDOPTREF x{F406} @Defop LDDICTQ x{F407} @Defop PLDDICTQ x{F40A} @Defop DICTGET x{F40B} @Defop DICTGETREF x{F40C} @Defop DICTIGET x{F40D} @Defop DICTIGETREF x{F40E} @Defop DICTUGET x{F40F} @Defop DICTUGETREF x{F412} @Defop DICTSET x{F413} @Defop DICTSETREF x{F414} @Defop DICTISET x{F415} @Defop DICTISETREF x{F416} @Defop DICTUSET x{F417} @Defop DICTUSETREF x{F41A} @Defop DICTSETGET x{F41B} @Defop DICTSETGETREF x{F41C} @Defop DICTISETGET x{F41D} @Defop DICTISETGETREF x{F41E} @Defop DICTUSETGET x{F41F} @Defop DICTUSETGETREF x{F422} @Defop DICTREPLACE x{F423} @Defop DICTREPLACEREF x{F424} @Defop DICTIREPLACE x{F425} @Defop DICTIREPLACEREF x{F426} @Defop DICTUREPLACE x{F427} @Defop DICTUREPLACEREF x{F42A} @Defop DICTREPLACEGET x{F42B} @Defop DICTREPLACEGETREF x{F42C} @Defop DICTIREPLACEGET x{F42D} @Defop DICTIREPLACEGETREF x{F42E} @Defop DICTUREPLACEGET x{F42F} @Defop DICTUREPLACEGETREF x{F432} @Defop DICTADD x{F433} @Defop DICTADDREF x{F434} @Defop DICTIADD x{F435} @Defop DICTIADDREF x{F436} @Defop DICTUADD x{F437} @Defop DICTUADDREF x{F43A} @Defop DICTADDGET x{F43B} @Defop DICTADDGETREF x{F43C} @Defop DICTIADDGET x{F43D} @Defop DICTIADDGETREF x{F43E} @Defop DICTUADDGET x{F43F} @Defop DICTUADDGETREF x{F441} @Defop DICTSETB x{F442} @Defop DICTISETB x{F443} @Defop DICTUSETB x{F445} @Defop DICTSETGETB x{F446} @Defop DICTISETGETB x{F447} @Defop DICTUSETGETB x{F449} @Defop DICTREPLACEB x{F44A} @Defop DICTIREPLACEB x{F44B} @Defop DICTUREPLACEB x{F44D} @Defop DICTREPLACEGETB x{F44E} @Defop DICTIREPLACEGETB x{F44F} @Defop DICTUREPLACEGETB x{F451} @Defop DICTADDB x{F452} @Defop DICTIADDB x{F453} @Defop DICTUADDB x{F455} @Defop DICTADDGETB x{F456} @Defop DICTIADDGETB x{F457} @Defop DICTUADDGETB x{F459} @Defop DICTDEL x{F45A} @Defop DICTIDEL x{F45B} @Defop DICTUDEL x{F462} @Defop DICTDELGET x{F463} @Defop DICTDELGETREF x{F464} @Defop DICTIDELGET x{F465} @Defop DICTIDELGETREF x{F466} @Defop DICTUDELGET x{F467} @Defop DICTUDELGETREF x{F469} @Defop DICTGETOPTREF x{F46A} @Defop DICTIGETOPTREF x{F46B} @Defop DICTUGETOPTREF x{F46D} @Defop DICTSETGETOPTREF x{F46E} @Defop DICTISETGETOPTREF x{F46F} @Defop DICTUSETGETOPTREF x{F470} @Defop PFXDICTSET x{F471} @Defop PFXDICTREPLACE x{F472} @Defop PFXDICTADD x{F473} @Defop PFXDICTDEL x{F474} @Defop DICTGETNEXT x{F475} @Defop DICTGETNEXTEQ x{F476} @Defop DICTGETPREV x{F477} @Defop DICTGETPREVEQ x{F478} @Defop DICTIGETNEXT x{F479} @Defop DICTIGETNEXTEQ x{F47A} @Defop DICTIGETPREV x{F47B} @Defop DICTIGETPREVEQ x{F47C} @Defop DICTUGETNEXT x{F47D} @Defop DICTUGETNEXTEQ x{F47E} @Defop DICTUGETPREV x{F47F} @Defop DICTUGETPREVEQ x{F482} @Defop DICTMIN x{F483} @Defop DICTMINREF x{F484} @Defop DICTIMIN x{F485} @Defop DICTIMINREF x{F486} @Defop DICTUMIN x{F487} @Defop DICTUMINREF x{F48A} @Defop DICTMAX x{F48B} @Defop DICTMAXREF x{F48C} @Defop DICTIMAX x{F48D} @Defop DICTIMAXREF x{F48E} @Defop DICTUMAX x{F48F} @Defop DICTUMAXREF x{F492} @Defop DICTREMMIN x{F493} @Defop DICTREMMINREF x{F494} @Defop DICTIREMMIN x{F495} @Defop DICTIREMMINREF x{F496} @Defop DICTUREMMIN x{F497} @Defop DICTUREMMINREF x{F49A} @Defop DICTREMMAX x{F49B} @Defop DICTREMMAXREF x{F49C} @Defop DICTIREMMAX x{F49D} @Defop DICTIREMMAXREF x{F49E} @Defop DICTUREMMAX x{F49F} @Defop DICTUREMMAXREF x{F4A0} @Defop DICTIGETJMP x{F4A1} @Defop DICTUGETJMP x{F4A2} @Defop DICTIGETEXEC x{F4A3} @Defop DICTUGETEXEC { dup sbitrefs tuck 1 > swap 1 <> or abort"not a dictionary" swap 1 u@ over <> abort"not a dictionary" } : @chkdicts { dup null? tuck { idict! not abort"cannot add key to procedure info dictionary" @procinfo ! } : @procinfo! // ( x v1 v2 -- ) { not 2 pick @procinfo@ and xor swap @procinfo! } : @procinfo~! // ( s i f -- ) { over @procdictkeylen fits not abort"procedure index out of range" over swap dup @procinfo~! 2dup @proclistadd 1 'nop does swap 0 (create) } : @declproc { 1 'nop does swap 0 (create) } : @declglobvar { @proccnt @ 1+ dup @proccnt ! 1 @declproc } : @newproc { @gvarcnt @ 1+ dup @gvarcnt ! @declglobvar } : @newglobvar variable @oldcurrent variable @oldctx Fift-wordlist dup @oldcurrent ! @oldctx ! { current@ @oldcurrent ! context@ @oldctx ! Asm definitions @proccnt @ @proclist @ @procdict @ @procinfo @ @gvarcnt @ @parent-state @ current@ @oldcurrent @ @oldctx @ 9 tuple @parent-state ! hole current! 0 =: main @proclist null! @proccnt 0! @gvarcnt 0! { bl word @newproc } : NEWPROC { bl word dup (def?) ' drop ' @newproc cond } : DECLPROC { bl word dup find { nip execute <> abort"method redefined with different id" } { swap 17 @declproc } cond } : DECLMETHOD { bl word @newglobvar } : DECLGLOBVAR "main" 0 @proclistadd dictnew dup @procdict ! @procinfo ! 16 0 @procinfo! } : PROGRAM{ { over sbits < { s>c } : }END> { }END> b> } : }END>c { }END>c s // This is the way how FunC assigns method_id for reserved functions. // Note, that Tolk entrypoints have other names (`onInternalMessage`, etc.), // but method_id is assigned not by Fift, but by Tolk code generation. 0 constant recv_internal -1 constant recv_external -2 constant run_ticktock -3 constant split_prepare -4 constant split_install { asm-mode 0 3 ~! } : asm-no-remove-unused { asm-mode 1 1 ~! } : asm-remove-unused // enabled by default { asm-mode 3 3 ~! } : asm-warn-remove-unused { asm-mode 4 4 ~! } : asm-warn-inline-mix { asm-mode 0 4 ~! } : asm-no-warn-inline-mix // disabled by default { asm-mode 8 8 ~! } : asm-warn-unused { asm-mode 0 8 ~! } : asm-no-warn-unused // disabled by default // ( c -- ) add vm library for later use with runvmcode { spec } : hash>libref // ( c -- c' ) { hash hash>libref } : >libref { dup "." $pos dup -1 = { drop 0 } { $| 1 $| nip swap (number) 1- abort"invalid version" dup dup 0 < swap 999 > or abort"invalid version" } cond } : parse-version-level { 0 swap "." $+ { swap 1000 * swap parse-version-level rot + swap } 3 times "" $= not abort"invalid version" } : parse-asm-fif-version { dup =: required-version parse-asm-fif-version asm-fif-version parse-asm-fif-version = 1+ { "Required Asm.fif version: " @' required-version "; actual Asm.fif version: " asm-fif-version $+ $+ $+ abort } if } : require-asm-fif-version { dup =: required-version parse-asm-fif-version asm-fif-version parse-asm-fif-version swap >= 1+ { "Required Asm.fif version: " @' required-version "; actual Asm.fif version: " asm-fif-version $+ $+ $+ abort } if } : require-asm-fif-version>= Fift definitions Asm ' <{ : <{ ' PROGRAM{ : PROGRAM{ ' asm-fif-version : asm-fif-version ' require-asm-fif-version : require-asm-fif-version ' require-asm-fif-version>= : require-asm-fif-version>= Fift