Re: Factor

Factor: the language, the theory, and the practice.

Building "cat"

Saturday, August 21, 2010

#command-line

One neat feature of Factor is the ability to create and deploy programs as compiled binaries – both CLI (command-line) or UI (graphical) applications.

I thought it might be fun to build the cat command-line program in Factor, and show how it can be deployed as a binary. From the man pages:

The cat utility reads files sequentially, writing them to the standard output. The file operands are processed in command-line order. If file is a single dash (’-’) or absent, cat reads from the standard input.

We’ll start by creating the cat vocabulary. You can either create the cat.factor file yourself, or use tools.scaffold to do it for you:

IN: scratchpad USE: tools.scaffold

IN: scratchpad "cat" scaffold-work
Creating scaffolding for P" resource:work/cat/cat.factor"

IN: scratchpad "cat" vocab edit

Begin the implementation by listing some imports and a namespace:

USING: command-line kernel io io.encodings.binary io.files
namespaces sequences strings ;

IN: cat

Printing each line from a stream is easy using the each-line word (flushing after each write to match the behavior of cat):

: cat-lines ( -- )
    [ write nl flush ] each-line ;

I chose to treat files (which might be text or binary) as binary, reading and writing 1024 bytes at a time. We check that the file exists, printing an error if not found:

: cat-stream ( -- )
    [ 1024 read dup ] [ >string write flush ] while drop ;

: cat-file ( path -- )
    dup exists?
    [ binary [ cat-stream ] with-file-reader ]
    [ write ": not found" write nl flush ] if ;

Given a list of files, with a special case for “-” (to read from standard input), we can cat each one:

: cat-files ( paths -- )
    [ dup "-" = [ drop cat-lines ] [ cat-file ] if ] each ;

Finally, we need an entry point that checks if command-line arguments have been provided:

: run-cat ( -- )
    command-line get [ cat-lines ] [ cat-files ] if-empty ;

MAIN: run-cat

Using the deploy-tool:

IN: scratchpad "cat" deploy-tool

Click “Save” to persist the deploy settings into a deploy.factor file, and “Deploy” to create a binary. You should see output like the following:

Deploying cat...
Writing vocabulary manifest
Preparing deployed libraries
Stripping manual memory management debug code
Stripping destructor debug code
Stripping stack effect checking from call( and execute(
Stripping specialized arrays
Stripping startup hooks
Stripping default methods
Stripping compiler classes
Finding megamorphic caches
Stripping globals
Compressing objects
Compressing quotations
Stripping word properties
Stripping symbolic word definitions
Stripping word names
Clearing megamorphic caches
Saving final image

And your binary should be in the same directory as your Factor installation (in a cat.app sub-directory on the Mac).

$ ls -hl cat.app/Contents/MacOS/cat 
-rwxr-xr-x  1 user  staff  421k Aug 21 11:11 cat.app/Contents/MacOS/cat*

$ cat.app/Contents/MacOS/cat
hello, world
hello, world
^D

The code for this is on my GitHub.