--- title: "QDOS Filters" type: "article" slug: "qdos-filters" url: "http://localhost/article/qdos-filters/" markdown_url: "http://localhost/article/qdos-filters.md" published_at: "2025-12-18T17:42:20+00:00" modified_at: "2026-06-21T23:21:33+00:00" featured_image: url: "http://localhost/wp-content/uploads/2022/04/20230809-041332.jpg" excerpt: "Filter programs are common in the UNIX world, but they are found to a varying extent on other platforms (such as QDOS) as well. In QDOS filters were introduced in 1984 with the QL-Toolkit (which later turned into Toolkit II). A filter program is typically a program that reads a stream of characters and makes…" category: - name: "QL Hacker's Journal" slug: "ql-hackers-journal" taxonomy: "category" url: "http://localhost/category/periodicals/ql-hackers-journal/" post_tag: - name: "Full Text" slug: "fulltext" taxonomy: "post_tag" url: "http://localhost/tag/fulltext/" - name: "Machine language programming" slug: "machine-language" taxonomy: "post_tag" url: "http://localhost/tag/machine-language/" - name: "QL" slug: "ql" taxonomy: "post_tag" url: "http://localhost/tag/ql/" model: - name: "Sinclair QL" slug: "sinclair-ql" taxonomy: "model" url: "http://localhost/model/sinclair-ql/" publication_r: id: 33686 title: "QL Hacker’s Journal" type: "periodical" url: "http://localhost/periodical/ql-hackers-journal/" authors: "Per-Erik Forssen" issue: "21" issues_articles: - id: 61557 title: "QL Hacker’s Journal 21" type: "issue" url: "http://localhost/issue/ql-hackers-journal-21/" pubdate: "June 1995" archive_link: true --- Filter programs are common in the UNIX world, but they are found to a varying extent on other platforms (such as QDOS) as well. In QDOS filters were introduced in 1984 with the QL-Toolkit (which later turned into Toolkit II). A filter program is typically a program that reads a stream of characters and makes some simple operation on it before passing it on. This function is not too different from what a filter attached to a water pipe will accomplish, thus the name filter. Filters may be connected in chains to perform more complex operations. Examples of what filters can do are converting the character stream to uppercase, encrypting it with a supplied key, etc. From QDOS a filter is started like this: ``` EX UPCASE,ram1_infile,ram2_outfile ``` The EX command is an enhanced version of EXEC that can pass arguments to the started programs. In this case the files ‘ram1_infile’ and ‘ram2_outfile’ are opened and the channel ID’s are passed to the program (placed on the (A7) stack). Our example filter will strip the input stream from ANSI ESC-codes. ESC-codes are produced by software generating output to a terminal. The name ESC-code is due to all codes starting with the ESC char, chr$(27). After that comes a varying number of argument chars. ESC-codes will, when recieved by the terminal, alter some of it’s settings. One code will for example switch to underline mode( chr$(27); ‘[4m’; ), while another will turn this mode off. This program will only remove some of the ESC-codes. ``` * * A filter to strip _log files from ESC sequences * Current version only strips codes generated by the UNIX * 'nn' program Release 6.5.0 #8 (NOV), * Kim F. Storm, 1991 * This program is (C) 1995 Per-Erik Forssen * Should assemble with Qmac, other assemblers will need * other directives. * * QDOS standard equates * myself equ -1 forever equ -1 err.nc equ -1 err.ef equ -10 mt.frjob equ $05 io.fstrg equ $03 io.sbyte equ $05 ut.err0 equ $ca * * equates special for this program * bufsz equ 8000 ch_lf equ $0A ch_cr equ $0D ch_esc equ $1B ch_brace equ $5B * * Macro to step through the read buffer * NEXTCHAR MACRO [.lab] move.b (a2)+,d1 ; get next char subq.w #1,d2 ; decrement count ENDM section program data 8192 ; 8K data space * bra.s ESCstrip dc.b '1.00' ; program version dc.w $4afb ; standard job dc.w 8 ; program name dc.b 'ESCstrip' ds.w 0 ; word align ESCstrip lea.l 0(a6,a4.l),a6 ; set buffer pointer move.w (sp)+,d7 ; pop no of chan id's subq.w #2,d7 ; should be be two bne.s ESC_exit move.l (sp)+,a0 ; input chan move.l (sp)+,a4 ; output chan * ESC_fstrg moveq #io.fstrg,d0 ; fetch a string move.w #bufsz,d2 ; maximum size moveq #10,d3 ; wait up to a fifth of a second move.l a6,a1 ; place it in buffer trap #3 move.l d0,d4 ; keep error code move.w d1,d2 ; and string length beq.s ESC_errtst ; null string? * move.l a6,a2 ; reset buffer pointer ESC_loop NEXTCHAR cmp.b #ch_cr,d1 ; ignore CR beq.s ESC_loop cmp.b #ch_esc,d1 ; at an ESC-code ? bne.s ESC_send NEXTCHAR cmp.b #ch_brace,d1 bne.s ESC_psend NEXTCHAR bsr.s SKIP_NUM cmp.b #'m',d1 beq.s ESC_lfsend cmp.b #'H',d1 beq.s ESC_loop cmp.b #'J',d1 beq.s ESC_loop cmp.b #'K',d1 beq.s ESC_loop cmp.b #';',d1 bne.s ESC_psend bsr.s SKIP1_NUM * ESC_lfsend moveq #ch_lf,d1 ; send a line feed instead bsr.s BPUT bra.s ESC_next ESC_psend move.b d1,d5 ; esc code unresolved; moveq #ch_esc,d1 ;let it pass through filter bsr.s BPUT move.b d5,d1 ESC_send bsr.s BPUT * ESC_next tst.w d2 bgt.s ESC_loop * ESC_errtst move.l d4,d0 ; test the return from read beq.s ESC_fstrg addq.l #-err.nc,d4 ;not enough time to finish? beq.s ESC_fstrg cmp.l #err.ef-err.nc,d4 ; end of file? bne.s ESC_exit moveq #0,d0 ; yes; exit without errors * ESC_exit move.w ut.err0,a2 ; signal error in SuperBASIC #0 jsr (a2) move.l d0,d3 ; return status to owner moveq #mt.frjob,d0 ; force remove moveq #myself,d1 ; myself trap #1 * * Subroutines * SKIP_NUM skips 0 or more digits in string * SKIP1_NUM skips 1 or more * SKIP_NUM SN_loop cmp.b #'0',d1 blt.s SN_end cmp.b #'9',d1 bgt.s SN_end SKIP1_NUM NEXTCHAR bra.s SN_loop SN_end rts ; number is now skipped * * BPUT sends char in d1.b to output * BPUT movem.l a1/d1,-(a7) exg a4,a0 ; use output chan moveq #forever,d3 ; timeout for write moveq #io.sbyte,d0 trap #3 exg a4,a0 ; use input chan movem.l (a7)+,a1/d1 rts end ``` The program includes many of the programming features used in assembler. For example the equ statements: ``` myself equ -1 ``` This corresponds to the C #define statements, i.e. all occurrences of ‘myself’ will be replaced with ‘-1’ at assembly. Then we have macros: ``` NEXTCHAR MACRO [.lab] move.b (a2)+,d1 ; get next char subq.w #1,d2 ; decrement count ENDM ``` This corresponds to the C++ inline functions (or perhaps some more advanced C #define directives). All occurrences of NEXTCHAR in the code will be replaced by the instructions between MACRO and ENDM. A macro can have arguments, but this feature is not used here. Then we have the BPUT, SKIP_NUM and SKIP1_NUM subroutines. They work much like functions, only that the argument passing is implicit. You will have to decide for each subroutine that for example registers D1 to D4 are arguments, and the result is returned in D0. For those who have never seen assembler before, see this as a brief introduction to how assembler programming works. For others, here is a neat way to write filters for QDOS.