fifis a Stack-based Programming Language in Clojure(script). It is interpreted, extensible, and simple. It was developed to be its own sandboxed scripting language to perform operations on clojure(script) applications.fifis based off of Forth, but has adopted clojures core libraries for familiarity.
(require'[fif.core:asfif])
(fif/reval"Hello World!".cr);;=> '()
;;<stdout>: Hello World!
(fif/reval11+);;=> '(2)
(fif/reval
fnyeaa!
#_"( n -- ) Prints yeeee{n}hhh!"
"yeeee".
0do"a".loop
"hhh!".cr
endfn
5yeaa!);;=> '()
;;<stdout>: yeeeeaaaaaahhh!
(fif/reval
fnfactorial
dup1>ifdup dec factorial * then
endfn
5factorial);;=> '(120)
;;
;;Most of the clojure functions are available, with more being ported
;;and tested.
;;
(fif/reval(1234) rest);;=> '((2 3 4))
(fif/reval(1234) first);;=> '(1)
(fif/reval(1234)5conj);;=> '((5 1 2 3 4))
(fif/reval[1234]5conj);;=> '([1 2 3 4 5])
;;
;;More Advanced Features
;;
;;Functional Programming
(fif/reval*+ [1234] reduce);;=> '(10)
(fif/reval*inc [1234] map);;=> '([2 3 4 5])
(fif/reval*even? [1234] filter);;=> '([2 4])
;;Inner Sequence Evaluation (Termed "Realizing" )
(fif/reval[41doi incloop]?);;=> '([2 3 4 5])
fifrequires clojure 1.9+
This could be relaxed to clojure 1.7 with interest.
For the latest version, please visitclojars.org
Leiningen/Boot
[fif-lang/fif"1.3.1"]
Clojure CLI/deps.edn
fif-lang/fif {:mvn/version"1.3.1"}
Gradle
compile'fif-lang:fif:1.3.1'
Maven
<dependency>
<groupId>fif-lang</groupId>
<artifactId>fif</artifactId>
<version>1.3.1</version>
</dependency>
In stack-based programming, operations are performed on data using
Postfix Notation:ex.1 2 +
.This is the complete opposite of
Polish Notationused in lisp languages: ex.+ 1 2
.
The basic principles behind how stack-based programming operates is by pushing values onto a stack, and having defined symbols, called wordsperform operations on the pushed stack values.
(fif/reval12);;=> '(1 2)
(fif/reval12+);;=> '(3)
Forthis one of the more well known languages which uses this approach, and it is used as a baseline for the implementation of fif.
Althoughfifis similar toforthin a lot of ways, I like to think thatfifis less restrictive, but also more error-prone (hopefully less so with later developments). Forth has a compile mode, which only allows certain defined words to be used while defining new words. None of this exists infif.Everything is interpreted the moment a dribble of data appears to the stack-machine.
;;conditionals are compile-mode only in Forth, but allowed in fif
(fif/reval10=if"Ya"else"Nah"then);;=> '( "Nah" )
;;do loop is compile-mode only in Forth, along with the rest of the
;;conditional-loops. All of this is allowed in fif.
(fif/reval40doiloop);;=> '(0 1 2 3 4)
;;defining functions inside functions doesn't exist in forth to the
;;best of my knowledge.
(fif/revalfnfunc_define_add
fnadd22+ endfn
endfn
func_define_add
2add2);;=> '(4)
Code is presented tofifin the form of the edn data format, which means that only valid data values in clojure are allowed withinfif.This comes as a huge advantage, since it meansfif has a wealth of data structures at its disposal, and allows for seamless interoperability within the clojure environment.
(fif/reval1has-flag? namespace/value.thing why!?!? {:a123} [123] #{:mental-asylum:ledger})
;;=> (1 has-flag? namespace/value.thing why!?!? {:a 123} [1 2 3] #{:ledger:mental-asylum})
(defnself-destruct[]"yes")
(fif/reval(self-destruct)fnself-destruct"no"endfn self-destruct);;=> '((self-destruct) "no" )
For a detailed breakdown on valid data that can be passed tofif please refer to theBuilt-in elementssection in theedn format github page.
fifmaintains a few operators for displaying to standard output.
;;Drop the Top value and display it on standard output
(fif/reval12.);;=> '(1)
;;<stdout>: 2
;;Carriage return is provided with `cr`
(fif/reval"Hello".cr"There!".cr);;=> '()
;;<stdout>: Hello
:: <stdout>: There!
;;<stdout>:
;;
;;Clojure equivalent print functions have been maintained
;;
(fif/reval"Hello World!"println);;=> '()
;;<stdout>: Hello World!
;;<stdout>:
(fif/reval"Hello World!"print);;=> '()
;;<stdout>: Hello World!
(fif/reval"Hello World!"prn);;=> '()
;;<stdout>: "Hello World!"
;;<stdout>:
(fif/reval"Hello World!"pr);;=> '()
;;<stdout>: "Hello World!"
Note that these examples are similar toLearn Forth in Y Minutes
;;
;;Arithmetic
;;
;;Addition
(fif/reval54+);;=> '(9)
;;Subtraction
(fif/reval54-);;=> '(1)
;;Multiplication
(fif/reval68*);;=> '(48)
;;Division
(fif/reval124/);;=> '(3)
;;Modulo
(fif/reval132mod);;=> '(1)
;;Negation
(fif/reval99negate);;=> '(-99)
;;Absolute Value
(fif/reval-99abs);;=> '(99)
;;Maximum and Minimum Value
(fif/reval5223max);;=> '(52)
(fif/reval5223min);;=> '(23)
;;Increment and Decrement Value
(fif/reval1inc);;=> '(2)
(fif/reval2dec);;=> '(1)
;;
;;Stack Manipulation
;;
;;Duplicate Stack Value
(fif/reval3dup dup);;=> '(3 3 3)
;;Swap First and Second Values
(fif/reval25swap);;=> '(5 2)
;;Rotate Top 3 Values
(fif/reval123rot);;=> '(2 3 1)
;;Drop Top Value
(fif/reval12drop);;=> '(1)
;;Drop the Second Value
(fif/reval123nip);;=> '(1 3)
;;
;;More Advanced Stack Manipulation
;;
;;Duplicate the Top Value, and place it between the Second Value and Third Value
(fif/reval1234tuck);;=> '(1 2 4 3 4)
;;Duplicate the Second Value, and place on the top
(fif/reval1234over);;=> '(1 2 3 4 3)
Conditionals produce the clojure equivalent booleantrue
and
false
values. However, conditional flags withinfifalso treat
0 asfalse
and any non-zero number astrue
.
Note: The implementation of this can be found atfif.stdlib.conditional/condition-true?
(fif/reval53<);;=> '(false)
(fif/reval55<=);;=> '(true)
(fif/reval10=);;=> '(false)
(fir/reval10not=);;=> '(true)
(fif/reval52>);;=> '(true)
(fif/reval31>=);;=> '(true)
The only conditional structures withinfifare:
<condition> if <true-body> then
<condition> if <true-body> else <false-body> then
Examples:
;;zero values are considered false
(fif/reval0if1then);;=> '()
(fif/revalnilif1then);;=> '()
(fif/revalfalseif1then);;=> '()
;;non-zero values are considered true
(fif/reval1if1then);;=> '(1)
(fif/reval-1if1then);;=> '(1)
(fif/revaltrueif1then);;=> '(1)
;;Anything else is evaluated by passing to `clojure.core/boolean`
(fif/reval[]if1then);;=> '(1)
(fif/reval0if1else2then);;=> '(2)
(fif/reval11-if1else2then);;=> '(2)
;;if conditions can be nested
(reval
fncheck-age
dup18<ifdrop"You are underage"else
dup50<ifdrop"You are the right age"else
dup50>=ifdrop"You are too old"else
then then then
endfn
12check-age
24check-age
51check-age);;=> '( "You are underage" "You are the right age" "You are too old" )
Functions withinfifare calledword definitionsand have the syntax:
fn <name> <body...> endfn
Functions are stored globaly within the stack machine. This holds true when you attempt to define functions while within a function.
Few Examples:
(fif/reval
fnsquare dup * endfn
5square);;=> (25)
(fif/reval
fnadd22+ endfn
fnadd4 add2 add2 endfn
4add4);;=> '(8)
There are currently four standard loops infif:
<end> <start> do <body> loop
<end> <start> do <body> <step> +loop
begin <body> <flag> until
begin <flag> while <body> repeat
Examples:
;;do loops are inclusive
(fif/reval20do"Hello!"loop);;=> '( "Hello!" "Hello!" "Hello!" )
;;do loops also have special index words i, j and k
(fif/reval20doiloop);;=> '(0 1 2)
;;These are useful for nested loops
(->>(fif/reval20do30doj ilooploop)
(partition2))
;;=> ((0 0) (0 1) (0 2) (0 3) (1 0) (1 1) (1 2) (1 3) (2 0) (2 1) (2 2) (2 3))
;;do loops have a special increment based loop with +loop
(fif/reval100doi2+loop);;=> '(0 2 4 6 8 10)
;;begin-until performs the action until its clause is true
(fif/revalbegin1trueuntil);;=> '(1)
(fif/revalbegin1falseuntil);;=> '(1 1 1 1 1........
(fif/reval0begin dup inc dup5= until);;=> '(0 1 2 3 4 5)
;;begin-while-repeat performs the action while its while clause is true
(fif/revalbeginfalsewhile1repeat);;=> '()
(fif/revalbegintruewhile1repeat);;=> '(1 1 1 1 1.......
(fif/reval0begin dup5< while dup inc repeat);;=> '(0 1 2 3 4 5)
;;You can break out of any loop prematurely using `leave`
(fif/revalbegintruewhile leave repeat);;=> '() No Infinite Loop!
(fif/reval0begintruewhile dup inc dup5=ifleave then repeat);;=> '(0 1 2 3 4 5)
fifuses the concept ofWord Referencing,which is a means of pushing already defined words onto the stack. This becomes useful for setting variables and for functional programming as shown in the next two sections.
;;Already defined words won't end up on the stack
(fif/reval22+);;=> '(4)
(fif/reval+);;ERROR
;;A word reference involves placing an asterisk '*' infront of the
;;word you want on the stack.
(fif/reval22*+);;=> '(2 2 +)
(fif/reval*+);;=> '(+)
;;These can be chained for deeper referencing
(fif/reval**+);;=> '(*+)
(fif/reval***+);;=> '(**+)
(fif/reval********+);;=>....
;;Multiplication remains unaffected
(fif/reval22*);;=> '(4)
fifsupports some of the usual functional programming idioms seen in other popular languages. The currently implemented functional programming operators arereduce,map,andfilter.
<fn ( xs x -- 'xs )> <coll> reduce
<fn ( item -- 'item )> <coll> map
<fn ( item -- boolean )> <coll> filter
(fif/reval*+ [1234] reduce);;=> '(10)
(fif/reval*inc [1234] map);;=> '((2 3 4 5))
(fif/reval*even? [12345] filter);;=> '((2 4))
(fif/reval*inc [1234] map);;=> '((2 3 4 5))
The base functional operators can also be passed a sequence in place of a function, which will be treated as a lambda expression.
(fif/reval(2+) [1234] map);;=> '((3 4 5 6))
(fif/reval(:eggsnot=) [:eggs:ham:green-eggs:eggs] filter)
;;=> '((:ham:green-eggs))
fifstrays away from Forth in the way it sets and gets variables. SincefifusesWord Referencing,the ability to get Word Variables simply requires you to place the word on the stack to retrieve the value. Setting the variable requires you to provide aWord Reference,as shown in the examples below.
Global variables withinfifare declared usingdef
,and are
treated as word definitions. They can be set using the word
operatorsetg
.Local variables are declared usinglet
,and can
be set programmatically usingsetl
.
Examples
(fif/reval
;;
;;Globally Scoped Variables
;;
*X22+ setg
X. cr;;=> '(4)
;;Set X to 10
defX10
;;Get X
X
;;Set X to 20
*X20setg
;;
;;Locally Scoped Variables
;;
;;Note that functions have a local dynamic scope.
letytrue
y;;=> '(true)
;;They can be set programmatically with `setl`
*yfalsesetl
y;;=> '(false)
)
Macrosare somewhat experimental, but for future macros, it would be interesting to see how easily it might be to manipulate the code stack in new and interesting ways. A very primitive macro system is implemented. As an example, I implemented an incomplete `?do` loop fromForth
Example:
(reval
macro?do
over over >
if
_! incdo!_
else
_!doleave!_
then
endmacro
fnyeaa!
#_"(n -- ) Prints yeaa with 'n' a's"
"yeeee".
0?do"a".loop
"hhh!".cr
endfn
0yeaa!
5yeaa!);;=> '()
;;<stdout>: yeeeehhh!
;;<stdout>: yeeeeaaaaahhh!
One interesting by-product of creatingfifwithin clojure is how easy it is to extendfiffrom within clojure. There is a wealth of functionality that can be easily included infifwith only a few lines of code.
As an example, i’m going to make two functions. One function that adds items to a vector, and another which retrieves the vector.
(def*secret-notes(atom[]))
(defnadd-note![s] (swap!*secret-notes conj s))
(defnget-notes[] @*secret-notes)
(add-note!"They're in the trees")
(add-note!{:date"March 14, 2018":name"Stephen Hawking"})
(get-notes);;=> [ "They're in the trees" {:date "March 14, 2018":name "Stephen Hawking" }]
I want two functions infifto closely resemble the clojure equivalents, notably:
add-note!,which takes one value, and returns nothing
get-notes,which takes no values, and returns the list
Using the default stack machinefif.core/*default-stack*
,we can
extend it to include this functionality:
(require'[fif.core:asfif])
(require'[fif.def:refer[wrap-procedure-with-arity
wrap-function-with-arity
set-word-function]])
;;Wrap add-note! as a procedure which accepts 1 value from the
;;stack. Note that the procedure wrapper does not return the result
;;of our function to the stack.
(defop-add-note!(wrap-procedure-with-arity1add-note!))
;;Wrap get-notes as a function. Note that the function wrapper will
;;return its result to the stack.
(defop-get-notes(wrap-function-with-arity0get-notes))
(defextended-stack-machine
(->fif/*default-stack*
(set-word-function'add-note! op-add-note!)
(set-word-function'get-notes op-get-notes)))
;;Let's take our new functionality for a spin
(reset!*secret-notes [])
(fif/with-stackextended-stack-machine
(fif/reval"I Hate Mondays"add-note!);;=> '()
(fif/reval-string"\ "Kill Switch: Pineapple\ "add-note!");;=> '()
(fif/revalget-notes));;=> '([ "I Hate Mondays" "Kill Switch: Pineapple" ])
More advanced functions can make use of the full stack machine, and
a few of these functions can be seen in thefif.stdlib.ops
namespace.
fifisn’t that useful interactively without facilities to capture stdout and stderr. A Programmable Repl (prepl) can be easily implemented within fif using `fif.core/prepl-eval`.
For this example, i’m going to create a prepl from the default-stackwhich will change state within an atom. Additional atoms will be used to capture stdout and stderr.
(require'[clojure.string:asstr])
(require'[fif.core:asfif])
(def*sm(atomfif/*default-stack*))
(def*stdout-results(atom[]))
(def*stderr-results(atom[]))
(defnprepl-reset![]
(reset!*sm fif/*default-stack*)
(reset!*stdout-results [])
(reset!*stderr-results []))
(defnoutput-fn
"Standard Output/Error Handler Function."
[{:keys[tag value]}]
(let[;;Remove platform specific newlines
value (str/replacevalue# "\r\n""\n")]
(cond
(=tag:out)
(swap!*stdout-results conj value)
(=tag:error)
(swap!*stderr-results conj value))))
(defnprepl[sinput]
(swap!*sm fif/prepl-eval sinput output-fn)
{:stack(->@*sm fif/get-stack reverse)
:stdout@*stdout-results
:stderr@*stderr-results})
(prepl"2 2");;=> {:stack '(2 2):stdout []:stderr []}
(prepl"+");;=> {:stack '(4):stdout []:stderr []}
(prepl"println");;=> {:stack '():stdout [ "4\n" ]:stderr []}
(prepl-reset!)
The fif prepl functionality works in clojurescript, however, clojurescript lacks a standard error output, so it is not likely the:error tag would appear to the output function.
Although this might not be taken as a feature,fifcan have clojure s-exps evaluated within its comfy confines. The default set offifevaluators over clojure data are subject to the same clojure reader shortfalls that prevent it from being used as a data format.
Note that reading in data as a string representation does not suffer from these shortfalls as discussed in another section
(fif/reval1#=(+11) +);;=> '(3) Yikes!
(defnboiling-point-c[]100)
(fif/reval#=(boiling-point-c)1+);;=> '(101) Russians!
However, the preferred way to include additional data withinfif is by either passing values onto the stackmachine, or by settingfif variables which can be accessed from within fif.
(require'[fif.core:asfif])
(require'[fif.stack-machine:asstack])
(require'[fif.def:refer[set-word-variable]])
(defnsecret-stack-machine
"Returns a stack machine with a `secret` value stored in the fif
variable 'secret"
[secret]
(->fif/*default-stack*
(set-word-variable'secret secret)))
(fif/with-stack(secret-stack-machine:fooey)
(fif/revalsecret));;=> (:fooey)
(defnpill-popping-stack-machine
"Returns a stack machine with the values within `pills` placed on
the stack"
[& pills]
(loop[sm fif.core/*default-stack*
pills pills]
(if-let[pill (firstpills)]
(recur(stack/push-stacksm pill)
(restpills))
sm)))
(fif/with-stack(pill-popping-stack-machine:pink:green:blue)
(fif/reval"The pill on the top of the stack is:"..))
;;=> '(:pink:green)
;;<stdout>: The pill on the top of the stack is::blue
An additional alternative was introduced, which is to generate the quoted form with additonally evaluated clojure code included through an escape sequence. If the escape sequence is provided, ‘%=, the next value in the sequence is evaluated as clojure code. This would be useful when generating code from a client to plug into a fif stack machine as a server command.
(require'[fif.core:asfif])
(require'[fif.client:refer[form-string]])
(defsecret-message"The Cake is a Lie")
(fif/reval-string(form-string"The secret message is:"%= secret-message str println))
;;<stdout>: The secret message is: The Cake is a Lie
;;<stdout>:
Although usingfiffrom within clojure might have its shortfalls, fifcan avoid these shortfalls of clojure by passing in strings containing EDN data.
The same unsafe example from before:
(require '[fif.core:as fif]) (fif/reval-string "1 1 +" );; => '(2) (fif/reval-string "1 #=(+ 1 1) +" );; ERROR ;; Unhandled clojure.lang.ExceptionInfo ;; No reader function for tag =. ;; {:type:reader-exception,:ex-kind:reader-error}
This means thatfifcould potentially (without liability on the author’s part) be used for remote execution. It could be used as a sandboxed environment which only extends to clojure functions which are deemed safe.
This brings me to the issue of erroneous infinite loops. Thefif stack machine has the ability to limit stack operation to a max number of execution steps.
(require'[fif.core:asfif])
(require'[fif.stack-machine:asstack])
(defnlimited-stack-machine[step-max]
(->fif/*default-stack*
(stack/set-step-maxstep-max)))
(defdefault-step-max200)
(defneval-incoming[s]
(let[sm (limited-stack-machinedefault-step-max)
evaluated-sm (fif/with-stacksm (fif/eval-strings))
max-steps (stack/get-step-maxevaluated-sm)
num-steps (stack/get-step-numevaluated-sm)]
(if(>=num-steps max-steps)
"Exceeded Max Step Execution"
(->evaluated-sm stack/get-stack reverse))))
(defincoming-fif-eval"3 0 do:data-value i loop")
(eval-incomingincoming-fif-eval);;=> (:data-value 0:data-value 1:data-value 2:data-value 3)
(definfinite-fif-eval"begin true while:data-value 1 repeat")
(eval-incominginfinite-fif-eval);;=> "Exceeded Max Step Execution"
(defmalicious-fif-eval"begin #=(fork-main-thread) false until")
(eval-incomingmalicious-fif-eval);;ERROR
;;Unhandled clojure.lang.ExceptionInfo
;;No reader function for tag =.
;;{:type:reader-exception,:ex-kind:reader-error}
fifhas the ability to start a socket repl server with a designated stack-machine which can be accessed through a raw socket connection. This has the benefit of providing a simple interface for configuring a server, while only exposing limited functionality.
(require'[fif.core:asfif])
(require'[fif.stack-machine:asstack])
(require'[fif.server.core:asfif.server])
(defserver-name"Example Socket Server")
(defserver-port5005)
(defcustom-stack-machine
(->fif/*default-stack*
;;prevents system error handler from throwing an error,
;;places it on the stack instead
stack/enable-debug))
(defnstart-socket-server[]
(fif.server/start-socket-servercustom-stack-machine server-name:portserver-port))
(defnstop-socket-server[]
(fif.server/stop-socket-serverserver-name))
Testing this server on linux can be done using netcat:netcat localhost 5005
If you are on Windows, it can be accessed with putty with these additional configuration options:
- SetConnection TypetoRaw
- Under theTerminalSetting Category, enableImplicit CR in every LF
fif supports a fairly straightforward commandline repl, which is located at `fif mandline/-main`. The commandline repl has the ability to load scripts containing fif/edn code, and also includes additional standard library word definitions for reading and writing files on the filesystem. These additional word definitions are located in the:stdlib.io group
The fif commandline can be accessed withlein run -- <arguments>
As of version 1.0.1,fifcan be used as a standalone scripting
language. Compilation into a native executable is done by using
GraalVMwith thenative-image
commandline-tool.
To generate this executable yourself:
- clone this repository
- make sure you haveleiningeninstalled
- download and unpack a copy ofthe graal repository
- set the environment variable GRAAL_HOME as the root path of this graal repository
- While at the root of the fif repository, run the
build-native.sh
script.
The generated executable should be placed in the./bin/ folder of the repository.
$ fif -e 2 2 + println
4
$ fif -h
fif Language Commandline repl/eval
Usage:
fif [options]
fif<filename>[arguments..] [options]
Options:
-h, --help Show this screen.
-e Evaluate Commandline Arguments
Website:
github /benzap/fif
Notes:
*Commandline Arguments are placedinthe word variable$vargs
*The:stdlib.io group includes additional io operationsforreading
and writing files
$ fif
Fif Repl
'help'forHelp Message
'bye'to Exit.
>2 2 + println
4
>bye
For now, bye!
The resulting binary starts incredibly fast (<20ms), and has the advantage of directly manipulating EDN configuration files.
$ fif -e'"./deps.edn" dup load-file [:deps fif] {:mvn/version "1.0.2" } assoc-in spit'
It can also be used like any standard scripting language. As an
example, i’m going to write a primitive script to add, remove and
list dependencies from a “deps.edn” file calledclj-deps
#!/usr/bin/env fif
def help-message"clj dependency tool
Usage:
clj-deps add <package> <version>
clj-deps remove <package>
clj-deps list
Example:
clj-deps add fif 1.0.2
"
*cargs $vargs count setg
*command $vargs first setg
*package $vargs second dup nil? notifread-string first then setg
*version $vargs2get setg
cargs3=
command"add"=
and
if
"deps.edn"read-file first
[:depspackage]?
{}:mvn/versionversion assoc assoc-in
"deps.edn"<> spit
else
cargs2=
command"remove"=
and
if
"deps.edn"read-file first
dup:depsget package dissoc:deps<> assoc
"deps.edn"<> spit
else
cargs1=
command"list"=
and
if
"deps.edn"read-file first
:depsget (dupfirst.":".second:mvn/versionget. crnil) <> map
else
help-message println
then then then
An example of it’s use:
$echo"{}">deps.edn
$ clj-deps add fif 1.0.2
$ clj-deps add clock 0.3.2
cat deps.edn
{:deps {fif {:mvn/version"1.0.2"}, clock {:mvn/version"0.3.2"}}}
$ clj-deps remove clock
$ clj-deps list
fif:1.0.2
$ clj-deps
clj dependency tool
Usage:
clj-deps add<package><version>
clj-deps remove<package>
clj-deps list
Example:
clj-deps add fif 1.0.2
You can pull the project from github. Clojure tests are run via
lein test
,and Clojurescript tests are run vialein doo
.
Clojurescript tests require you to havenode
on your
Environment PATH.
I welcome any and all pull requests that further improve what is currently here, especially things which further improve security and improve error messages.
I’m still not sure where to go with respect to the standard library, and i’m open to suggestions for making manipulation of clojure data as painless as possible.
A few things to look out for:
Implementation in Clojurescriptincluded since 0.3.0-snapshotRegex Support (#”” tagged literal is not valid EDN)use ‘regex’ word definitionImproved Error MessagesSocket Replincluded since 0.4.0-snapshotCommandline Replincluded since 0.4.0-snapshotProgrammable Repl in Clojure and Clojurescriptincluded since 0.4.0-snapshotImproved repl word definitionsOn-GoingAdditional Standard Library Word DefinitionsOn-Going- Improved Fif Macros
- A Time Machine Debugger
- Starting Forth - Online Book
- A Simple Forth Interpreter in Clojure - Blog Post
- Learn Forth In Y Minutes
- Extensible Data Notation - Github Page
- GForth - Forth Implementation of the GNU Project
fifis meant to be a play onforth.The nameforthwas originally meant to be speltfourth,but had to be reduced in order to fit within the restrictions of computers at the time of it’s creation, and so the name stuck. I recommend you check out the wiki pagefor an interesting read.
It also helps to note that fif kind of sounds like youhave a lisp:)
It’s at the point where it is a viable scripting language for my own projects. It has the benefits of being completely sandboxed, and with the addition of the socket repl server, it could be used as an alternative to exposing functionality for setting and getting server configuration data, or even for automating certain functionality with external scripts.