SkillAgentSearch skills...

Fjs

FORTH-like language for Javascript / Node

Install / Use

/learn @mark-hahn/Fjs
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

fjs

Concatenative (FORTH-like) language for Javascript / Node

Overview

FJS (the name is from "Forth-like JavaScript") is a concatenative stack language, like FORTH, Postscript, and Factor. It is highly integrated with Javascript. The compiler runs in JS, FJS can load and call any JS file/module, JS modules can load and call FJS modules, all variables are JS types, and JS code can be embedded in FJS.

While FJS has many features (see next section), I'd like to point out one big one for the Node community. Stack languages naturally support continuations. FJS takes advantage of this to convert the Javascript callbacks into a pure linear sync code style. See the http server example below.

Features

  • Easy to learn. There is almost no syntax.
  • Excellent for REPL usage due to simple definitions and small amount of typing.
  • All the features of old stack languages updated to modern functional style.
  • Compatible with Javascript in all ways. Compatible with all javascript modules.
  • Runs on the server or in the browser. Compiled code is standard Javascript.
  • Compiles very fast.
  • Allows writing simple linear code while calling async functions.
  • Context (continuation) objects allow meaningful complete stack traces.

Status (Version 0.1.0)

While FJS code can be compiled and run, the project is pre-pre-alpha. The last changes broke the backwards compatability badly and there will probably be more such changes.

Some missing features ...

  • Plans are in the works for a webpage based REPL with sandbox.
  • Better error reporting. Currently debugging is done by an exhaustive execution trace.
  • Unit testing.
  • Optimized compilation for faster execution. Speed is unmeasured at this point. (Even though FJS semantics assume a stack, much of the code can be optimized to remove the stack behind the scenes.)

Why Use A Stack Language?

Stack languages are concise, simple, and powerful. See the Wikipedia entry. They are effecitively a macro language excellent for DSP and REPL usage. Stack programs have virtually no syntax. They are just a list of constants and functions. See this discussion from the Factor website for an extended look at the advantages of stack languages.

Stack languages look confusing to beginners until the RPN (see note) argument order becomes familiar. Once you understand the stack usage you will realize how simple it is that each word executes immediately and in order. It becomes quite readable.

Note: fjs actually uses polish notation, PN, not reverse polish notation, RPN.

Installation / Usage

FJS compiles using Node and FJS executable code has only been tested in Node. FJS is not available on NPM yet. FJS is written entirely in coffeescript but it is not needed for normal usage.

To try out FJS now you will need to clone the repo from git@github.com:mark-hahn/fjs.git. Keep all files in the one fjs directory and use node fjs test to compile a FJS source file test.jfs to test.js. Then execute test.js with node test. A windows batch file is included for automating the complete cycle of compiling the compiler, compiling the runtime modules, comiling the FJS source code, and executing the result. A bash equivalent is coming soon.

The runtime does not yet have error reporting but it will be supported soon including stack traces that are transparent to callbacks, unlike JS traces. You can set the variable debugRuntime in the top of the compiler code to see an execution trace showing the stack on every instruction. You can change the variable directly in fjs.js or recompile the coffee version.

Hello World

Here is the mandatory Hello World example which should look somewhat familiar to FORTH programmers ...

. 'Hello World'

This is stack-based code but it is JS under the covers. For example the dot (print) command is actually just a synonym for console.log.

Note to FORTH users: fjs executes from right-to-left, not the FORTH left-to-right. This is done to make the resulting code much more like Javascript. So it is console.log 'Hello World', not 'Hello World' console.log.

Documentation

This informally documents the FJS language as of this writing.

Comments

Currently there are only line-based comments. Just as in Javascript a double slash // starts a comment which goes to the next line feed character \n. This is the only place in the language where a line feed has any meaning.

. + 1 1   // prints "2" on stdout
. * 1 1   // prints "1" on stdout

Execution

FJS is unique, even among other FORTH-like languages, in that execution always proceeds from one word to the next without ever going backwards and never skipping. Conditional execution is handled by using functions with the if command. Loops are managed by calling functions repeatedly with while commands, etc. Also, using iterator functions like map and reduce reduce the need for loops. This simple linear execution, even when calling async functions, makes FJS quite readable once you learn the command words and the stack model becomes familiar.

Words

A word can be any mix of characters other than white-space and periods. There are some other format restrictions (see Word Modifiers below). Valid words examples are myvar, 2, /+X, i++, and (*&^. All extra white space is ignored in FJS, including line endings, so there is no concept of a "line". All words are translated to legal JS variable names internally. E.g. /+X would be represented internally as _slash__plus_X.

Each word in FJS is either a Javascript constant, Javascript variable, or a Javascript function evaluation. A function is called with arguments popped from the stack and in most cases a return value is pushed on the stack. All function words, including simple ones like print (.), are implemented as normal Javascript functions. See the fjs-primitives file for examples.

Named Variables

Named variables are simply Javascript variables of the same name, except that illegal characters are escaped (see the Words section). Assignnment to a variable is accomplished by adding an equals sign to the end of the name. The top item on the stack is popped and stored in that variable. For example, x= will pop the top stack item and assign the value to the variable x. Remember that accessing a variable is as simple as including the name. So the following will assign the value 3 to x, retrieve the value, and print it.

x= 3 // assign 3 to variable x
. x  // prints "3"

Functions

A function is defined in FJS by a set of parentheses surrounding code. There must be white space around the parens like the words. Every function definition in FJS is anonymous until assigned to a variable (ala Coffeescript and Postscript).

Here is a function that prints "hello world". In the first line the function is defined and executed immediately. In the last two lines the function is pushed on the stack (see : modifer in the Word Modifers section), the function is then assigned to a named variable, and the function is then called by simply including the variable name.

( . "hello world" )                   // creates and runs function, prints "hello world"
printGreeting= :( . "hello world" )   // creates function and assigns it to a variable
printGreeting						  // runs function, prints "hello world"

Word Modifers

There are several characters that have special meaning when added to a word or function. They are the modifiers :, ., <, and =. These sigils are not just annotative. They modify the program behavior. The : is prepended to the beginning of the word and the rest are appended to the end of the word.

The : modifier is prepended to the beginning of a variable name or function. It blocks the word's evalution and puts it directly on the stack. In the case of a variable name it pushes the name itself as a string. So this is actually just shorthand for a string constant with no white space. (This is called a "symbol" in Ruby). In the case of a function name, it pushes the actual function on the stack instead of calling it. This also works with anonymous functions.

.  x x= 1		// prints "1"
. :x x= 1		// prints "x"
.  Math.min		// prints "Infinity"
. :Math.min     // prints "[Function: min]"
.  ( + 1 2 )    // prints "3"
. :( + 1 2 )	// prints "[Function]"

The . modifier, not to be confused with the dot print word, acts much the same way as it does in Javascript. The dot is appended to the end of a variable. It pops an object off the stack and references the named property of the object, leaving the property value (or function result) on the stack. These two lines are functionally identical.

. Math.min  1 2 3   // prints "3" (Math.max(1, 3, 2))   Leaves empty stack
. min. Math 1 2 3   // prints "3" (Math.max(1, 3, 2))   Leaves empty stack

The < modifier is appended to the end of any function. It has an optional form with a number <n. It overrides the default number of items to pop off the stack and pass to the called function as arguments. When there isn't a number after the <, the entire stack is passed as arguments and emptied. The default number of arguments depends on the funcion but for javascript functions it is always the entire stack.

.    1 2 3				// pops one item and prints "1"
.<   1 2 3				// empties stack and prints "1 2 3"
.<2  1 2 3				// pops two items and prints "1 2"
. Math.min 	 1 2 3		// empties stack and prints "3"
. Math.max<2 1 2 3		// pops two items and prints "2"

The = suf

View on GitHub
GitHub Stars51
CategoryDevelopment
Updated1mo ago
Forks5

Languages

JavaScript

Security Score

95/100

Audited on Mar 2, 2026

No findings