Factor has programmable syntax, a feature that allows for concise source code, reducing repetition and allowing the programmer to express forms and intent with minimal tokens. As an example of this, today I want to discuss constants.
You can define a word with a constant value, using syntax like this:
CONSTANT: three 3
Someone on our Factor Discord server asked if it was possible to define multiple constants in one syntax expression, to avoid the line noise of defining them one-by-one.
So, instead of these four definitions:
CONSTANT: foo 1
CONSTANT: bar $[ 2 sqrt ]
CONSTANT: baz $ bar
CONSTANT: qux \ foo
We could instead make this syntax:
SYNTAX: CONSTANTS:
";" [
create-word-in
[ reset-generic ]
[ scan-object define-constant ] bi
] each-token ;
Breaking that down into steps:
SYNTAX:
indicates we’re defining new syntaxCONSTANTS:
is the name of our new syntax word";"
defines the terminator that will end our constant definitionseach-token
will process each token until it hits the terminatorFor each constant definition, it performs these steps:
create-word-in
creates a new word in the current vocabularyreset-generic
clears any generic word propertiesscan-object
reads and parses the next valuedefine-constant
makes it a constant with the parsed valueAnd now this expression works, reducing the visual noise in our source code:
CONSTANTS:
foo 1
bar $[ 2 sqrt ]
baz $ bar
qux \ foo
;
As an aside, the different syntaxes used above are:
1
is just a token parsed as a number literal$[ ... ]
evaluates the code inside at parse time$
gets the value of another constant\
gets the word object itself rather than its valueFactor’s syntax parsing words allow a great deal of flexibility in making custom DSL-style syntax forms work nicely to reduce repetition, and generate code with less effort.
I’m not sure if this is worth adding to the standard library or not, but it’s neat!
Over a decade ago, Chris Kempson created the Base16 theme framework for creating color palettes of 16 colors that can be used to provide theming of user interfaces. These have been commonly supported by many text editors, with some developers gravitating toward setting their favorite theme in every user interface that supports it.
A few years ago, this framework and the many themes that became popular in it were forked into the Tinted Theming project described in a post called Base16 Project Lives On. You can view their gallery of Base16 themes which gives a good sense of the variety and utility of these color schemes having commonly recognizable names such as dracula, mocha, solarized, and more.
I was reminded of this recently in a discussion around a recent contribution to change the scrollbar and button implementations to not use images, but to draw the scrollbars using the colors configured in the user’s theme.
Since 2021, the ui.theme.base16 vocabulary has allowed theming the Factor user interface by choosing a base16-theme-name and setting base16-mode. We have just improved our support for Base16 theme support by adding all the current styles from the Tinted Theming schemes list.
So, now you can try solarized-dark:
IN: scratchpad "solarized-dark" base16-theme-name set-global
IN: scratchpad base16-mode
Or perhaps greenscreen:
IN: scratchpad "greenscreen" base16-theme-name set-global
IN: scratchpad base16-mode
Or any of the other 270 named color schemes now available!
Enjoy!
One of the interesting aspects of a concatenative language like Factor is that blocks of logic can be easily extracted and easily reused since they apply logic to objects on the stack.
For example, if this was a word that operated on stack values:
: do-things ( a b -- c d )
[ sqrt * ] [ swap sqrt + ] 2bi ;
One change we could easily make is to extract and name the two pieces of logic:
: calc-c ( a b -- c ) sqrt * ;
: calc-d ( a b -- d ) swap sqrt + ;
: do-things ( a b -- c d )
[ calc-c ] [ calc-d ] 2bi ;
We could also convert it to operate on local variables:
:: do-things ( a b -- c d )
a b sqrt * a sqrt b + ;
And extract those same two pieces of logic:
:: calc-c ( a b -- c ) a b sqrt * ;
:: calc-d ( a b -- d ) a sqrt b + ;
:: do-things ( a b -- c d )
a b calc-c a b calc-d ;
But, notice that we have to specify that the local variable a
and b
have to be put back on the stack before we can call our extracted words that
make the computations.
Today, someone on the Factor Discord server asked about this very issue, wanting to have extractable pieces of logic that would effectively be operating on nested local variables, wherever they are used. Inspired by the goal of don’t repeat yourself and the convenience of extracting logic that operates on the data stack.
Specifically, they wanted to be able to take blocks of logic that operate on named variables, and extract them in a similar manner to the logic blocks that operate on the stack – offering this hypothetical syntax as the goal:
EMIT: calc-c ( a b -- c ) a b sqrt * ;
EMIT: calc-d ( a b -- d ) a sqrt b + ;
:: do-things ( a b -- c d )
calc-c calc-d ;
Let’s try and build real syntax that allows this hypothetical syntax to work.
First, we make a tuple to hold a lazy variable binding:
TUPLE: lazy token ;
C: <lazy> lazy
Then, we need a way to generate temporary syntax words in a similar manner to temporary words:
: define-temp-syntax ( quot -- word )
[ gensym dup ] dip define-syntax ;
We create temporary syntax words to convert each named references to lazy variables:
: make-lazy-vars ( names -- words )
[ dup '[ _ <lazy> suffix! ] define-temp-syntax ] H{ } map>assoc ;
Given a quotation that we have parsed in an emit description, we can build a word to replace all these lazy variables by looking them up in the current vocabulary manifest:
: replace-lazy-vars ( quot -- quot' )
[ dup lazy? [ token>> parse-word ] when ] deep-map ;
And, finally, create our emit syntax word that parses a definition, making lazy variables that are then replaced when the emit word is called in the nested scope:
SYNTAX: EMIT:
scan-new-word scan-effect in>>
[ make-lazy-vars ] with-compilation-unit
[ parse-definition ] with-words
'[ _ replace-lazy-vars append! ] define-syntax ;
Now, let’s go back to our original example:
EMIT: calc-c ( a b -- c ) a b sqrt * ;
EMIT: calc-d ( a b -- d ) a sqrt b + ;
:: do-things ( a b -- c d )
calc-c calc-d ;
Does it work?
IN: scratchpad 1 2 do-things
--- Data stack:
1.4142135623730951
3.0
Yep! That’s kind of a neat thing to build.
I have added this syntax in the locals.lazy vocabulary, if you want to try it out.
I’m not sure how useful it will be in general, but it is always fun to build something new with Factor!
Battlesnake is “a competitive game where your code is the controller”. In particular, in answering the question “What is Battlesnake?”, the documentation says:
In this game, each Battlesnake is controlled in real-time by a live web server, responding to the Battlesnake API. It navigates the game board based on your algorithm, trying to find food, avoid other Battlesnakes, and survive as long as possible. Battlesnakes can be built using any tech stack you’d like, and we encourage you to step outside of your comfort zone.
It is also a very neat set of episodes of “Coding Badly” from almost two years ago that talks about building battlesnakes using Factor. In particular, they use a live-coding style to explore the development environment, build web servers using the furnace web framework, and learn how to use and deploy their program!
I did not know about these videos until today, but I thought it makes a nice series to share with the world. I love it when people build things using Factor and am always glad to hear about it!
More information is also available on the @BattlesnakeOfficial GitHub organization, as well as an archive of the Coding Badly implementation and a different Factor battlesnake library by another contributor.
“Life can only be understood backwards; but it must be lived forwards.” — Kierkegaard
I’m very pleased to announce the release of Factor 0.100!
OS/CPU | Windows | Mac OS | Linux |
---|---|---|---|
x86 | 0.100 | 0.100 | |
x86-64 | 0.100 | 0.100 | 0.100 |
Source code: 0.100
This release is brought to you with over 1400 commits by the following individuals:
Aditya Aryaman Das, Alex
null
Maestas, Alexander Ilin, Andy Kluger, Bhargav Shirin Nalamati, Charlie Weismann, Dave Carlton, David Enders, Doug Coleman, Evgenii Petrov, Giftpflanze, Ikko Eltociear Ashimine, J. Ryan Stinnett, Jean-Marc Lugrin, John Benediktsson, Keldan Chapman, Limnanthes Serafini, Marc Michael, Michael Raitzam, Michael Thies, Pragya Pant, Raghu Ranganathan, Rebecca Kelly, Rudi Grinberg, Sandesh Pyakurel, Sebastian Strobl, Shruti Sen, Surav Shrestha, Val Packett, @Capital-EX, @Smoothieewastaken, @TheWitheredStriker, @TryAngle, @chunes3, @inivekin, @nomennescio, @olus2000.
Besides some bug fixes and library improvements, I want to highlight the following changes:
xmlns
that were accidentally changed to https
Some possible backwards compatibility issues:
focusable-child*
now returns f
to indicate parent should be focusedPEG:
and PARTIAL-PEG:
forms, not delay to first invocationmacosx
to macos
deg>rad
and rad>deg
to math.functions vocabularydivisor?
to support mixed numbers (floats and integers)integer-log10
and integer-log2
to math.functions vocabularyhttp.download
vocabularyread-json
returns a single object, use read-jsons
to read multiplebase32-crockford
and base32hex
vocabulariesI would also like to bring particular recognition to Raghu Ranganathan, also known as @razetime, who was an incredible developer with an incredibly good attitude and contributing member to many technical communities including code golfing and various programming languages including Factor. We are very sad that he passed away a couple of months ago and would like to have this moment dedicated in his memory.
Factor is a concatenative, stack-based programming language with high-level features including dynamic types, extensible syntax, macros, and garbage collection. On a practical side, Factor has a full-featured library, supports many different platforms, and has been extensively documented.
The implementation is fully compiled for performance, while still supporting interactive development. Factor applications are portable between all common platforms. Factor can deploy stand-alone applications on all platforms. Full source code for the Factor project is available under a BSD license.
download
wordspromises
tools.image-analyzer
ssize_t
stream-read-c-ptr
and read-c-ptr
assocs.extras
, removed with-assoc
set-of
from assocs
sunrise
, sunset
, and solar-noon
class<=
for anonymous-predicate
contrast-text-color
to select white/black text on dark/light backgroundssequence-case
, fix 3tri*
command-line-options
for easy options parsingcontributors.
and make the changelog respect .mailmap
filenodelay
LIKE" column"
syntaxbad-escape
format-directive
EBNF wordrglob
for recursive glob<t:meta>
tag to be able to specific any meta attributesnths
I"
interpolated string syntax, allow format directives to be usedunder
?move-file
move-file
to properly replace existing filesif-file-exists
combinators and (file-writer-secure)
default-cache-directory
to work in MacPorts environmentsafe-replace-file
and safe-modify-file
<connected-pair>
strip-ansi-escapes
and format-ansi-tables
read-json
to read-jsons
, added read-json
that reads a single object/etc/ld.so.cache
(such as NixOS)setlocale
supportgelu
, stable-softmax
, and stable-log-softmax
binary-bits
tuple<k-permutations>
for k=0squared-euclidian-distance
and normalized-squared-euclidian-distance
, aliases for taxicab-distance
and chessboard-distance
weighted-randoms-as
deg>rad
and rad>deg
), math.functions.integer-logs, and integer-sqrt
, added fma
(fused-multiply-add)<matrix-by>
>digits
and digits>
?read-msgpack
and read-msgpacks
json.http
vocabulary<cheapest-chat-completion>
for ease-of-use with “gpt-4o-mini”, add timestamps to the list-models apimulti-texture
scalingN
for new game a lotqualified-names?
to allow word names to be prettyprinted as fully-qualifiedcompose-all
random-bits*
to random-bits-exact
, rename the *-random-float
distributions to *-random
, add *-distribution
types, added more of them, defined a base-random
that allows a better not-a-random-generator
error to be produced in some casesrand()
is used to generate full range of 32-bit numbersdomain-stats
count=
, faster longest-subseq
lastn
, ?lastn
, set-lastn
get+increment
to consume
, and change next
to return the next elementproduct-each
, product-map
, and product-find
significantly fasterdeep-clone
dupdd
\r\n\v\f\x1c\x1d\x1e\x85\u002028\u002029
macosx
to macos
VOCAB:
syntaxVOCAB:
syntaxclose-all-windows
DISPLAY
os-var for graphical capabilityDISPLAY
os-var for graphical capabilityupdate-command-map
f focusable-child
busy loop~/.factor-history
filekm^2
and more aliasesvocab-exists?
no longer throw bad-vocab-name
<t:meta>
in child templatesuninterned-word
predicate, undefined-word
error classqdoc
and sparql
modeszmq-error
to be an error class2024b
Building a “cash register” is an often used example project, from places like the freeCodeCamp’s Javascript Algorithms and Data Structures Project “Cash Register” or codecademy’s “Building a Cash Register” along with other examples like the Simple Cash Register in Python.
I thought it would be fun to write about building something similar, but not the same, in Factor.
We are going to make a few assumptions:
Here are our units of change along with their descriptions:
CONSTANT: COINS {
{ 10000 "$100" }
{ 5000 "$50" }
{ 2000 "$20" }
{ 1000 "$10" }
{ 500 "$5" }
{ 200 "$2" }
{ 100 "$1" }
{ 25 "quarters" }
{ 10 "dimes" }
{ 5 "nickels" }
{ 1 "pennies" }
}
If we want to make change, we can generate it using something like the greedy algorithm to find minimum number of coins, starting with the largest denomination possible and iterating to smaller ones:
: make-change ( n -- assoc )
COINS [ [ /mod swap ] dip ] assoc-map swap 0 assert= ;
For convenience, we can make a formatting word to format our coins into dollars:
: $. ( n -- )
100 /f "$%.2f\n" printf ;
And now a word to print out the change we made:
: change. ( n -- )
"CHANGE: " write dup $. make-change [
'[ _ "%d of %s\n" printf ] unless-zero
] assoc-each ;
We can store the amount owed and the amount paid in dynamic variables:
INITIALIZED-SYMBOL: owed [ 0 ]
INITIALIZED-SYMBOL: paid [ 0 ]
Using that, we can make a word to display the balance due:
: balance. ( -- )
"OWED: " write owed get-global $.
"PAID: " write paid get-global $. ;
A word to add a charge, increasing the amount owed:
: charge ( n -- )
"CHARGE: " write dup $.
owed [ + ] change-global balance. ;
A word to make a payment, providing change if the amount paid is greater than the amount owed:
: pay ( n -- )
"PAY: " write dup $.
paid [ + ] change-global balance.
paid get-global owed get-global - dup 0 >=
[ change. 0 owed set-global 0 paid set-global ] [ drop ] if ;
And a word to cancel a transaction, refunding any paid amounts:
: cancel ( -- )
"CANCEL" print
0 owed set-global
paid [ change. 0 ] change-global ;
Using a word that parses input into a number of pennies:
: parse-$ ( args -- n )
"$" ?head drop string>number 100 * round >integer ;
We can then define a set of commands using the command-loop vocabulary:
CONSTANT: COMMANDS {
T{ command
{ name "balance" }
{ quot [ drop balance. ] }
{ help "Display current balance." }
{ abbrevs { "b" } } }
T{ command
{ name "charge" }
{ quot [ parse-$ charge ] }
{ help "Charge an item." }
{ abbrevs { "c" } } }
T{ command
{ name "pay" }
{ quot [ parse-$ pay ] }
{ help "Pay with money." }
{ abbrevs { "p" } } }
T{ command
{ name "cancel" }
{ quot [ drop cancel ] }
{ help "Cancel transaction." }
{ abbrevs { "x" } } }
}
And then define the loop that we run as MAIN:
: cash-register-main ( -- )
"Welcome to the Cash Register!" "$>"
command-loop new-command-loop
COMMANDS [ over add-command ] each
run-command-loop ;
MAIN: cash-register-main
And you can see an example from running it:
Welcome to the Cash Register!
$> c 10.23
CHARGE: $10.23
OWED: $10.23
PAID: $0.00
$> c 15.37
CHARGE: $15.37
OWED: $25.60
PAID: $0.00
$> p 100.00
PAY: $100.00
OWED: $25.60
PAID: $100.00
CHANGE: $74.40
1 of $50
1 of $20
2 of $2
1 of quarters
1 of dimes
1 of nickels
It could be fun to extend this example to have an inventory of purchasable items, allow users to ring up these items instead of a series of charges, maybe implement taxable items and discounts, display and print receipts, handle refunds, handle available bills and coins when making change, support other currencies, and other features that you might find in a more “complete” or “real-world” cash register.
The code for this is on my GitHub.
As close as I can tell, Factor is the result of contributions from around 180 contributors over the past 20 years.
Recently, I was reminded of a tool that can produce graphs showing some aspects of contributions to git repositories. The tool is called Git of Theseus written in the Python programming language, which can be used to generate a series of interesting plots showing statistics over time about a project. A similar tool is Hercules, which claims to be a bit faster and is written in the Go programming language.
We can look at code written in each year which, aside from the first few years, mostly continues to exist in the latest version. In addition, we see a healthy and increasing chart over time:
When wondering about the half-life of code, we might want to see how long a particular line of code continues to exist in the project. We can see that 50% of them are still existing after 5 years:
Many of those early contributions came from Slava Pestov, Doug Coleman, Chris Double, and others, and we continue to benefit from the impressive early work that they did for the Factor programming language. We can plot author statistics, and see the large blocks of contributions over time by various authors:
And, as a percentage of lines of code, see that beginning with almost 100% of the source code contributed by Slava Pestov, more recently we have around 20% each from Slava Pestov, Doug Coleman, and myself as well as several other significant authors:
We can generate a more detailed breakdown of these lines of code by language using tokei, but in particular see that we have over 388,000 lines of Factor source code in our latest development version:
$ tokei .
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Language Files Lines Code Comments Blanks
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Factor 4952 503452 388845 25717 88890
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
It was pretty great to generate these graphs, and to reminisce about all the vocabularies available so far, and ponder all those still yet to be written.
Happy coding!
planet-factor is an Atom/RSS aggregator that collects the contents of Factor-related blogs. It is inspired by Planet Lisp.