[ planet-factor ]

John Benediktsson: std::flip

Morwenn posted a blog about implementing a std::flip operation in C++:

This is basically walking up the tree from the child node as if it were a linked list. The reverse operation either implies walking through two children nodes, or simply flipping the order of parameters, which is where std::flip intervenes:

auto is_descendant_of = std::flip(is_ancestor_of);

// This property should always hold
assert(is_descendant_of(node1, node2) == is_ancestor_of(node2, node1));

Spoiler: the std::flip operator is not part of the C++ standard library, although an implementation is providing at the end of the blog post in around 90 lines of code.

Still, I thought it would be fun to implement in Factor.

As it turns out, we already have a flip word that modifies a sequence, essentially by returning the transpose of a matrix. One could argue that transpose might be a better name for that operation. In any event, let’s focus on implementing the std::flip operation.

How would we reverse the arguments to a word?

  • a b can become b a by calling swap.
  • a b c can become c b a by calling swap rot.
  • a b c d can become d c b a by calling swap rot roll.

We can generalize this into a macro by repeatedly calling -nrot:

MACRO: nreverse ( n -- quot )
    0 [a..b) [ '[ _ -nrot ] ] map [ ] concat-as ;

And then show that it works:

IN: scratchpad { } [ 0 nreverse ] with-datastack .
{ }

IN: scratchpad { "a" } [ 1 nreverse ] with-datastack .
{ "a" }

IN: scratchpad { "a" "b" } [ 2 nreverse ] with-datastack .
{ "b" "a" }

IN: scratchpad { "a" "b" "c" } [ 3 nreverse ] with-datastack .
{ "c" "b" "a" }

IN: scratchpad { "a" "b" "c" "d" } [ 4 nreverse ] with-datastack .
{ "d" "c" "b" "a" }

IN: scratchpad { "a" "b" "c" "d" "e" } [ 5 nreverse ] with-datastack .
{ "e" "d" "c" "b" "a" }

Note: this has been added to the shuffle vocabulary.

Using this, we can build some syntax that takes the next token and searches for a matching word with that name, and then calls it after reversing the inputs:

SYNTAX: flip:
    scan-word [ stack-effect in>> length ] keep
    '[ _ nreverse _ execute ] append! ;

As an example, we will use the 4array word that returns an array consisting of four arguments from the stack.

IN: scratchpad 10 20 30 40 4array .
{ 10 20 30 40 }

IN: scratchpad 10 20 30 40 flip: 4array .
{ 40 30 20 10 }

We could have different syntax for flipping arbitrary code – first parsing a quotation and then infer the stack-effect and then inline a reversed argument version.

SYNTAX: flip[
    parse-quotation [ infer in>> length ] keep
    '[ _ nreverse @ ] suffix! ;

We can try that out with a simple block of code:

IN: scratchpad 1 2 3 flip[ [ 10 * ] tri@ ] call 3array .
{ 30 20 10 }

And only a few lines of code in total.

Pretty cool!

Tue, 30 Sep 2025 03:00:00

John Benediktsson: Scream Cipher

Seth Larson wrote about a Scream Cipher:

You’ve probably heard of stream ciphers, but what about a scream cipher 😱? Today I learned there are more “Latin capital letter A” Unicode characters than there are letters in the English alphabet. You know what that means, it’s time to scream:

We can use bidirectional assocs to keep a single cipher data structure that efficiently maps into and out of the cipher:

CONSTANT: cipher $[
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "AÁĂẮẶẰẲẴǍÂẤẬẦẨẪÄǞȦǠẠȀÀẢȂĀĄ"
    zip >biassoc
]

: >scream ( str -- SCREAM )
    [ ch>upper cipher ?at drop ] map ;

: scream> ( SCREAM -- str )
    [ cipher ?value-at drop ] map ;

And then give it a try!

IN: scratchpad "FACTOR!" >scream .
"ẰAĂẠẪȦ!"

IN: scratchpad "ẰAĂẠẪȦ!" scream> .
"FACTOR!"

Fun!

Sun, 21 Sep 2025 15:00:00

John Benediktsson: Environment Variables

Factor has an environment vocabulary for working with process environment variables on all the platforms we currently support: macOS, Windows, and Linux.

Recently, I noticed that .NET 9 added support for empty environment variables. This was particulary relevant due to a test failure of the new Dotenv implementation on Windows. It turns out that we inherited the same issue that earlier .NET versions had, which is an inability to disambiguate an unset environment variable from one that was set to the empty string. This issue has now been fixed in the latest development version.

Before:

IN: scratchpad "FACTOR" os-env .
f

IN: scratchpad "" "FACTOR" set-os-env
IN: scratchpad "FACTOR" os-env .
f

After:

IN: scratchpad "FACTOR" os-env .
f

IN: scratchpad "" "FACTOR" set-os-env
IN: scratchpad "FACTOR" os-env .
""

IN: scratchpad "FACTOR" unset-os-env
IN: scratchpad "FACTOR" os-env .
f

There might be other cross-platform environment-related topics to investigate, such as an open issue to look into case-preserving but case-insensitive environment variables on Windows.

PRs welcome!

Fri, 19 Sep 2025 15:00:00

John Benediktsson: HiDPI

HiDPI is a name for high resolution displays, sometimes called retina displays. A long long time ago, I added support for Retina Displays on macOS using Factor. But, they have not been well supported on either Linux or Windows platforms.

That ends today!

Linux

Some users have seen the “small window” problem on Linux, where on high resolution displays the Factor UI listener was rendered super tiny:

This is now fixed, it renders at the appropriate resolution detecting the screen it is launched on, or using the GDK_SCALE environment variable:

There has been one report that this works in the Gnome environments but not on KDE, so we might still have a few code changes necessary to make this more universal. And we also still need to switch from using our older GTK integration to the newer one with clean support for Wayland.

Windows

Other users have noticed the blurry text on Windows, due to using a legacy compatibility mode:

This is now fixed, rendering with the correct scaling factor:

It has been tested with 200% and 300% scaling factors. It is possible that intermediate scaling factors like 150% are not well supported and additional tweaks might be necessary to make this more universal.

Currently, on all three supported platforms, we use a global scaling factor which does not allow for moving Factor windows cleanly between screens with different scaling factors, for example when using HDMI on presentations, etc.

PRs welcome!

Tue, 2 Sep 2025 15:00:00

John Benediktsson: Neovim

Neovim is a modern implementation of a vim-like editor. It started as a refactor, but “not a rewrite but a continuation and extension of Vim”. It does have some ability to load plugins built in Vimscript, but most new plugins seem to be written using the Lua programming language.

Factor has many different editor integrations supporting various text editors as well as plugins for some that provide additional features. One of these is the factor.vim plugin for Vim, which I happen to use frequently.

In the Big Omarchy 2.0 Tour, DHH presents about the Omarchy customization of Linux. I noticed that they have a pretty nice Neovim integration, particularly with the system themes. It turns out to be based somewhat on the Lazyvim system.

In any event, I wondered about how easy it would be to make a Neovim plugin for Factor. It isn’t fully necessary as there is support for Vimscript plugins and the Factor one works pretty well out of the box. However, I thought I’d ask Claude Code to go off and YOLO an implementation based on the existing one. Thankfully this was not a FAFO moment, and after a few cycles it came back with something that mostly works!

This is available in the factor.nvim repository and should be pretty easy to integrate into your Neovim setup. Perhaps give it a try and see what you think? I’ve been using it and it seems to work pretty well.

Wed, 27 Aug 2025 15:00:00

John Benediktsson: New Icon

Encouraged by a fun rant about MacOS Tahoe’s Dead-Canary Utility App Icons, the reality that Apple is moving into the wonderful squircle-filled future, and the particularly annoying fact that legacy icons look terrible on macOS Tahoe – we have a new icon for Factor!

The latest development version includes new icon files in both PNG and SVG formats. And these are being used across macOS, Windows, and Linux builds. And, it might be burying the lede, but this is a particularly good time to do this as we finally have high-resolution “HiDPI” support working on Windows and Linux.

The next release is likely to be a good one!

Wed, 27 Aug 2025 01:00:00

John Benediktsson: TA-Lib

The TA-Lib is a C project that supports adding “technical analysis to your own financial market trading applications”. It was originally created in 2001 and is well-tested, recently released, and popular:

200 indicators such as ADX, MACD, RSI, Stochastic, Bollinger Bands etc… See complete list…

Candlestick patterns recognition

Core written in C/C++ with API also available for Python.

Open-Source (BSD License). Can be freely integrated in your own open-source or commercial applications.

Of course, I wanted to be able to call the library using Factor. We have a C library interface that makes it pretty easy to interface with C libraries.

First, we add the library we expect to load:

<< "ta-lib" {
    { [ os windows? ] [ "libta-lib.dll" ] }
    { [ os macos?   ] [ "libta-lib.dylib" ] }
    { [ os unix?    ] [ "libta-lib.so" ] }
} cond cdecl add-library >>

LIBRARY: ta-lib

Then, we can define some types and some library functions to calculate the relative strength index:

TYPEDEF: int TA_RetCode

FUNCTION: TA_RetCode TA_RSI ( int startIdx, int endIdx, double* inReal, int optInTimePeriod, int* outBegIdx, int* outNBElement, double* outReal )
FUNCTION: int TA_RSI_Lookback ( int optInTimePeriod )

We use a simple code generator to define all the functions, as well as wrappers that can be used to call it:

:: RSI ( real timeperiod -- real )
    0 int <ref> :> outbegidx
    0 int <ref> :> outnbelement
    real check-array :> inreal
    inreal length :> len
    inreal check-begidx1 :> begidx
    len 1 - begidx - :> endidx
    timeperiod TA_RSI_Lookback begidx + :> lookback
    len lookback make-double-array :> outreal
    0 endidx inreal begidx tail-slice timeperiod outbegidx outnbelement outreal lookback tail-slice TA_RSI ta-check-success
    outreal ;

And, now we can use it!

IN: scratchpad 10 10 randoms 3 RSI .
double-array{
    0/0.
    0/0.
    0/0.
    50.0
    62.16216216216216
    31.506849315068497
    46.38069705093834
    32.33644859813084
    59.75541967759867
    66.53570603189276
}

You’ll note that the first few values are 0/0. which represents a NaN when we don’t have enough data to compute an answer – either because we are in the lookback phase or because the inputs have NaNs.

For convenience, we convert the inputs to double-array to perform the calculation, but if the input is already a double-array then there is not any data conversion cost.

There are some advanced techniques including use of the Abstract API for meta-programming, default values for parameters, candlestick settings, streaming indicator support, and documentation that we probably should think about adding as well.

This is available on my GitHub.

Sun, 24 Aug 2025 15:00:00

John Benediktsson: String Length

I was reminded recently about a great article about unicode string lengths:

It’s Not Wrong that "🤦🏼‍♂️".length == 7

But It’s Better that "🤦🏼‍♂️".len() == 17 and Rather Useless that len("🤦🏼‍♂️") == 5

This comes at a time of excessive emoji tsunami thanks to the proliferation of large language models and probably lots of Gen Z in the training data sets. Sometimes emojis are fun and useful like in Base256Emoji and sometimes it can get carried away like in the Emoji Kitchen.

I have written about Factor’s unicode support before and wanted to use this example to show a bit more about how Factor represents text using the Unicode standard.

IN: scratchpad "🤦" length .
1

IN: scratchpad "🤦🏼‍♂️" length .
5

Wat.

Well, what is happening is that the current strings vocabulary stores Unicode code points. This can be both useful and useless depending on the task at hand. We can print out which ones are used in this example:

IN: scratchpad "🤦🏼‍♂️" [ char>name . ] each
"face-palm"
"emoji-modifier-fitzpatrick-type-3"
"zero-width-joiner"
"male-sign"
"variation-selector-16"

When a developer expresses a need to store or retrieve textual data, they likely need to know about character encodings. In this case, we can see the number of bytes required to store this string in different encodings:

IN: scratchpad "🤦🏼‍♂️" utf8 encode length .
17

IN: scratchpad "🤦🏼‍♂️" utf16 encode length .
16

IN: scratchpad "🤦🏼‍♂️" utf32 encode length .
24

But, what if we just want to know how many visual characters are in the string?

IN: scratchpad "🤦🏼‍♂️" >graphemes length .
1

This is covered in The Absolute Minimum Every Software Developer Must Know About Unicode in 2023, which is also a great article and covers this as well as a number of other aspects of the Unicode standard.

Sat, 23 Aug 2025 15:00:00

Blogroll


planet-factor is an Atom/RSS aggregator that collects the contents of Factor-related blogs. It is inspired by Planet Lisp.

Syndicate