|The Revised Maclisp Manual||Page A-17|
|SFA||Concept||Software File Array|
There are many instances when a MacLISP programmer wishes to write a function to simulate an arbitrary I/O device. That is, he wishes to use the primitives provided in LISP for doing I/O (such as READ and PRINT) yet wishes the target/source of the characters to be completely arbitrary. Maclisp has a mechanism called an SFA (Software File Array) to handle this situation.
An SFA consists of a function and some associated local storage bound up in an SFA object. In order to satisfy the above goals, an SFA object may be used in almost all places that a file-object can be used. Note that the existence of SFA's does not obviate the need for the current New-I/O implementation of files. SFA's are strictly a user entity, and the files are still needed in order to communicate with the operating system.
The SFA handler is a user-defined function which accepts 3 arguments. Its first argument is the SFA object on which it is to act, its second argument is a symbol indicating the operation to be performed, and the third argument is any data needed for that operation. The SFA object needs to be supplied to the function because one SFA handler may be associated with many different SFA objects. There are some operations used by the system (TYI, TYO, etc...). The user is free to define new operations and to use them by calling the SFA directly.
As there are predefined uses for many of the system I/O functions that currently exist, when these functions are translated to SFA operations they must retain the semantics they have when applied to file-objects. Since this is the case, any SFA that expects to interface directly to the standard I/O routines must know about the difference between binary and character-oriented operations. File objects can do either fixnum-I/O operations (e.g., IN and OUT) or ascii-I/O operations (e.g., TYI, TYO, READ, PRINT, ...) but not both; most SFA's will probably only handle one type of I/O or the other, but they are not prevented from handling both.
The user can explicitly send an operation message to an SFA by using SFA-CALL. Usually, however, he can just call normal file operation functions and they will do the SFA-CALL implicitly. The advantage of not using explicit calls to SFA-CALL is that the code can then operate on normal files and not just SFA's.
Some sample uses for SFA's are provided at the end of this chapter.
(SFAP (SFA-CREATE #'(LAMBDA N NIL) 0. 'DEMO)) => T
Creating and Using SFAs
|SFA-CREATE||Function||(SFA-CREATE fn i pname)|
When the SFA-CREATE is done, the SFA is immediatley invoked with a WHICH-OPERATIONS call. A mask is then created corresponding to the internal functions that the SFA knows how to do. This mask is used for fast error-checking when a predefined operation is handed an SFA (e.g. TYO).
The third arg, pname, may be any lisp form. The sfa object will print something like #SFA-|pname|-nnnnn.
|SFA-CALL||Function||(SFA-CALL sfa operation data)|
This is a way of manually invoking the handler for an SFA on an arbitrary operation with arbitrary data. e.g., (TYO 1 sfa) is equivalent to (SFA-CALL sfa 'TYO 1). The user of SFA-CALL is responsible for checking that operation is supported by sfa's handler; however, the operation needn't be one of the standard ones described in this section.
Local Data Storage in SFAs
|SFA-GET||Function||(SFA-GET sfa selector)|
Reads the local storage of sfa. If the second arg, selector, is a fixnum, then it is a subscript into the user's local storage array to be accessed. This number may range from 0 to the maximum as specified when the SFA object is created (see SFA-CREATE).
If second arg, selector, is a symbol, then one of the “named” locations is accessed. Valid names are: FUNCTION, the SFA's handler function; WHICH-OPERATIONS, a list of operations of interest to the system that the SFA can perform (a subset of the SFA's WHICH-OPERATIONS list); PNAME, the object to print as the “name” of the SFA; PLIST, a general property list, for use by the user; and XCONS, a “correspondent” for a bi-directional SFA (akin to the TTYCONS concept for ttys---see documentation on the TTYCONS options to STATUS and SSTATUS).
|SFA-STORE||Function||(SFA-STORE sfa selector val)|
Stores val in the location specified by the second arg, selector. The possible values which selector may take on are the same as those accepted by SFA-GET.
It is strongly recommended that the SFA function never be changed as the effects of such an action are expressly not guaranteed by the implementors.
General SFA Messages
This section pertains to messages which may be sent both to input and output SFAs.
No arguments will be passed to the SFA. There is no clear global semantic meaning of this.
This message is sent when (STATUS FILEMODE sfa) is done. The SFA should return a list of adjectives describing itself. If the SFA cannot support the FILEMODE operation (as determined by a system-performed WHICH-OPERATIONS check), then Lisp will handle the request by returning a list which has the form: ((SFA) . operations), where operations are the result of a WHICH-OPERATIONS call on the SFA.
This message is sent when (LENGTHF file) is done. The return value should be a fixnum specifying the file's length.
Input-Specific SFA Messages
This section pertains to messages which are only of interest to SFAs doing input. If the SFA is to support normal LISP reading other than TYI, it must support at least TYI and UNTYI, and preferably TYIPEEK also.
The SFA should return a fixnum representing a character. This operation may be called from a READ, READLINE, or a straight TYI (the SFA cannot depend upon being able to determine the context). The data argument is the value to return when EOF is reached (this is true for all input functions including IN).
The value returned should be a fixnum that is the next binary value in the input stream. The argument is the EOF value.
Output-Specific SFA Messages
This section pertains to messages which are only of interest to SFAs doing output.
The SFA should empty its output buffer to the logical “device” to which it is connected.
The SFA will receive a list of the args given to CURSORPOS as data (with the file arg removed). If the list is empty, the current cursorpos (horizontal . vertical) should be returned. Otherwise, data is a list of the form (hpos vpos) or (sym [val]), to be interpreted as the CURSORPOS function would.
The SFA should try to remove display of the last character output or echoed to this stream.
SFAs: Sample Usage
;;; An SFA for reading from a string instead of a file. ;;; Make an abstraction for our SFAs internal storage. (defmacro char-buffer (sfa) `(sfa-get ,sfa 0)) (defun string-stream-handler (self op data) ; a handler for our SFA (caseq op ((which-operations) '(tyi untyi tyipeek init)) ((tyi) (pop (char-buffer self))) ((untyi) (push data (char-buffer self)) t) ((tyipeek) (car (char-buffer self))) ((init) (setf (char-buffer self) (exploden data))) (t (error "Unsupported SFA operation" `(sfa-call ,self ,op ,data))))) (defun read-from-string (string-to-read-from) ; a user of our SFA (let ((string-stream (sfa-create #'string-stream-handler 1. ;one slot for local storage "StringStream"))) (sfa-call string-stream 'init string-to-read-from) (read string-stream)))
;; Test the function (read-from-string "(a . (b . nil))") => (A B) ;; Note that as in a file, a space MUST terminate a toplevel symbol. (read-from-string "foo ") => FOO (read-from-string "foo") ;(READ-EOF #SFA-|StringStream|-70752) END OF FILE WITHIN READ ;; Note also that extra characters are ignored by our function (read-from-string "abc def") => ABC
;; Functions like FLATSIZE and EXPLODE could have been defined ;; using SFAs. Here is an example of how... ;; Compiler declaration for free variable (defvar *flatsize* 0. ;Always bound - toplevel value is actually never used "For communication between a flatsize sfa and its caller") (defun flatsize-handler (self op data) ;Define a handler (caseq op ((which-operations) '(tyo)) ((tyo) (if (not (minusp data)) ;negative values aren't chars (setq *flatsize* (1+ *flatsize*)))) (t (error "Unsupported SFA operation" `(sfa-call ,self ,op ,data))))) (defvar *flatsize-sfa* (sfa-create #'flatsize-handler 0. "Flatsize Counter") "Counts characters that would be typed out by incrementing *FLATSIZE*") (defun print-width (form printer) ;a generalized FLATSIZE (let ((*flatsize* 0.)) (funcall printer form *flatsize-sfa*) ;for side-effect *flatsize*)) ;; Define our own functions which we hope are just like the ;; system-provided functions FLATSIZE and FLATC. (defun my-flatsize (x) (print-width x #'prin1)) (defun my-flatc (x) (print-width x #'princ))
(my-flatsize "A Test") => 8. (my-flatc "A Test") => 6.
;; Compiler declaration for free variable (defvar *exploded* nil ;Always bound - toplevel value is actually never used "For communication between an explosion sfa and its caller") (defun explosion-handler (self op data) ;Define a handler (caseq op ((which-operations) '(tyo)) ((tyo) (cond ((not (minusp data)) (push data *exploded*)))) (t (error "Unsupported SFA operation" `(sfa-call ,self ,op ,data))))) (defvar *explode-sfa* (sfa-create #'explosion-handler 0. "Janitor") "Piles up characters from an explosion backwards in the *EXPLODED* var.") (defun create-explosion (object printer) ;generalized exploding operator (let ((*exploded* nil)) (funcall printer object *explode-sfa*) (nreverse *exploded*))) ;; Here we define our own functions which are like the system functions (defun my-exploden (x) (create-explosion x #'princ)) (defun my-explodec (x) (mapcar #'ascii (my-exploden x))) (defun my-explode (x) (mapcar #'ascii (create-explosion x #'prin1)))
(my-explode '|A B|) => (/| A | | B /|) (my-exploden '|A B|) => (65. 32. 66.) (my-explodec '|A B|) => (A | | B)
|The Revised Maclisp Manual (Sunday Morning Edition)|
Published Sunday, December 16, 2007 06:17am EST, and updated Sunday, July 6, 2008.