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:
( scratchpad ) USE: tools.scaffold ( scratchpad ) "cat" scaffold-work Creating scaffolding for P" resource:work/cat/cat.factor" ( 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:
( 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.
John, I really like your posts. Is there a way you could post about developing GUI? Or showing a simple PNG? I'm having trouble finding info online.
ReplyDeleteDoes not quite work on 0.93:
ReplyDeleteThe input quotations to "if" don't match their expected effects
Input
[ cat-lines ]
Expected
(( ..a -- ..b ))
Got
(( -- x ))
Input
[ cat-file ]
Expected
(( ..a -- ..b ))
Got
(( -- ))
Right, I was missing a "drop" in the post (was correct on Github). It's fixed now.
ReplyDelete@Guillermo: Thanks! I plan on doing a few posts about GUI's soon. And I've got some image work coming up too.
ReplyDeleteNote that there is a race condition: you should not test for the existence of the file before opening it, but try to open the file and catch the error if there is one.
ReplyDelete@Sam: Yep, a classic race condition. I thought it would be simpler to demonstrate with an existence check.
ReplyDeleteMy original code used exception handling to trap the file system error as you suggest, but it seemed too advanced. Probably I should have kept it, though!