0 [IF]
Forth to HTML converter
Main contributors:
Brad Eckert brad1NO@SPAMtinyboot.com Original author
Ed Beroset berosetNO@SPAMmindspring.com Fixed HTML bugs
Dirk Busch dirkNO@SPAMschneider-busch.de Added some features
Revision 10. See bottom for revision history.
This ANS Forth program is public domain. It translates ANS Forth to colorized
HTML. Hyperlinks to the ANS Forth draft standard are inserted for all ANS
standard words. Hyperlinks to user definitions are included.
Usage: HTML FileName Generates HTML file from Forth source.
Output file is Filename with .HTM extension.
Q [forth code] Outputs HTML for 1 line to screen
Keep in mind that whatever path you use for the input filename will be in the
output files, so don't use a drive letter etc. if the HTML is intended for
distribution on CD or a web site.
A framed version of the output file has a "_f.htm" extension. The left frame
is an index of words and the words that use them. The right frame is source.
If you open the index file (_i.htm extension) and click on links, source will
be in a separate window. Still usable but frames are easier.
Q is for debugging. You can use "linenum ?" to show the line number if an
ABORT occurs. The HTML is about 8 times as big as the Forth source because of
all the links, color changes and whitespace.
INCLUDEd files produce corresponding HTML pages. If a file can't be found,
it is skipped. Otherwise it is nested.
When you INCLUDE this file some redefinition complaints may occur. That's
okay since you won't be loading an application on top of this. You can make
a text version of this file by cut-and-paste to a text editor. The browser's
save-as-text function will work too. This file is only 80 columns wide. Note
that the browser may wrap after 80 columns when saving as a text file.
Users of specific Forths can extend the hyperlink table to point to anchors
in a glossary for that particular Forth.
This code has been tested under Win32forth, SwiftForth, VFX and Gforth.
You can change the following bold/italic/nestable options:
[THEN]
ONLY FORTH ALSO DEFINITIONS
\ ------------------------------------------------------------------------------
\ Configuration - You can change the options:
0 VALUE bold \ T if bold text
0 VALUE italic \ T if italic comments
1 VALUE nestable \ T if INCLUDE nested files
1 VALUE linksource \ T link to the org. file /7/
1 VALUE frames \ T if using frames /9/
: dpanspath S" .\" ; \ path to the ANS-Files /7/
: copyright S" " ; \ copyright to output at /7/
\ the bottom of the HTML-file
0 CONSTANT scheme ( Color scheme ) \ 0 = light background, 1=dark
\ ------------------------------------------------------------------------------
: undefined ( -- f ) BL WORD FIND NIP 0= ;
undefined NOOP [IF] : NOOP ; [THEN]
undefined BOUNDS [IF] : BOUNDS OVER + SWAP ; [THEN]
undefined /STRING [IF] : /STRING TUCK - >R CHARS + R> ; [THEN]
undefined C+! [IF] : C+! SWAP OVER C@ + SWAP C! ; [THEN]
undefined FDROP [IF] : FDROP ; ( no floating point? fake it ) [THEN]
undefined >FLOAT [IF] : >FLOAT DROP C@ [CHAR] 0 [CHAR] 9 1+ WITHIN ; [THEN]
undefined SCAN [IF] : SCAN ( addr len char -- addr' len' )
>R BEGIN DUP WHILE OVER C@ R@ <> WHILE 1 /STRING REPEAT THEN R> DROP ; [THEN]
undefined SKIP [IF] : SKIP ( addr len char -- addr' len' )
>R BEGIN DUP WHILE OVER C@ R@ = WHILE 1 /STRING REPEAT THEN R> DROP ; [THEN]
: +PLACE ( addr len a -- ) 2DUP 2>R COUNT CHARS + SWAP MOVE 2R> C+! ;
: PLACE ( addr len a -- ) 0 OVER C! +PLACE ;
: UPC ( c -- C ) DUP [CHAR] a [CHAR] z 1+ WITHIN IF 32 - THEN ;
0 VALUE outf \ output to file
1 VALUE screen-only \ screen is for testing
: werr ABORT" Error writing file" ;
: out screen-only IF TYPE ELSE outf WRITE-FILE werr THEN ;
: outln screen-only IF TYPE CR ELSE outf WRITE-LINE werr THEN ;
WORDLIST CONSTANT hyperlinks \ list of hyperlinks
WORDLIST CONSTANT superlinks \ hyperlinks that can't change
VARIABLE attrib
: /a ( -- ) attrib @ IF S" " out THEN 0 attrib ! ;
: ,$ ( a len -- ) DUP C, BOUNDS ?DO I C@ C, LOOP ; \ text to dictionary
: ,| ( -- ) [CHAR] | WORD COUNT -TRAILING ,$ ;
: line ( a line# -- ) 0 ?DO COUNT + LOOP COUNT out ; \ output one string
: boiler ( addr -- ) BEGIN COUNT DUP WHILE 2DUP + >R outln R> REPEAT 2DROP ;
: ital( ( -- ) italic IF S" " out THEN ;
: )ital ( -- ) italic IF S" " out THEN ;
: newline ( -- ) S"
" outln ;
: xcr ( -- ) S" " outln ; \ /9/
CREATE misctext \ various attribute strings
,| |
,| |
,| |
,| " name="main" target="_self"> |
,| |
,| " target="main"> | \ 17
,| " target="idx"> |
0 C,
VARIABLE color \ current color
VARIABLE infont \ within tag
: misctext 2 line ;
: fontcolor ( color -- ) \ change font color
1 infont !
misctext 5 line 0 HEX <# # # # # # # #> out "> ;
: col ( color -- ) \ define a font color
CREATE , DOES> @ color ! ;
HEX
scheme 0 = [IF] \ light background
808080 col unknown
008000 col commentary
990000 col numeric
FF0000 col errors
990080 col values
000000 col userwords
009999 col userdefiner
CC00CC col variables
0000FF col core_ws \ core is slightly lighter blue
0000FF col core_ext_ws
0000FF col block_ws
0000FF col double_ws
0000FF col exception_ws
0000FF col facilities_ws
0000FF col file_ws
0000FF col fp_ws
0000FF col local_ws
0000FF col malloc_ws
0000FF col progtools_ws
0000FF col searchord_ws
0000FF col string_ws
[ELSE] \ black background
808080 col unknown
00FF00 col commentary
FF8080 col numeric
FF0000 col errors
FF00FF col values
FFFFFF col userwords
00FFFF col userdefiner
FF80FF col variables
8080FF col core_ws
8080FF col core_ext_ws
0000FF col block_ws
0000FF col double_ws
0000FF col exception_ws
0000FF col facilities_ws
0000FF col file_ws
0000FF col fp_ws
0000FF col local_ws
0000FF col malloc_ws
0000FF col progtools_ws
0000FF col searchord_ws
0000FF col string_ws
[THEN]
DECIMAL
CREATE begin_header \ begin of HTML file part 1
,| |
,| |
,| |
,| |
,| |
,| |
,| |
,| |
0 C,
CREATE mid_header \ begin of HTML file part 2
,| |
,| |
0 C,
CREATE end_header_part1 \ end of HTML file part 1 /7/
,|
|
,| |
0 C,
CREATE end_header_part2 \ end of HTML file part 2 /8/
,|
|
,| HTMLized by Forth2HTML ver 10
|
,| |
,| |
0 C,
: setcolor ( -- ) \ select next color
color @ fontcolor ;
: closefont ( -- )
infont @ IF
misctext 8 line 0 infont ! THEN ;
\ In order to represent all ASCII chars as text, some puncuation needs to be
\ changed to make it HTML compatible.
VARIABLE bltally
: outh ( a n -- ) \ HTMLized text output
999 bltally !
BOUNDS ?DO I C@ CASE
[CHAR] & OF S" &" out ENDOF
[CHAR] < OF S" <" out ENDOF
[CHAR] > OF S" >" out ENDOF
[CHAR] " OF S" "" out ENDOF
[CHAR] © OF S" ©" out ENDOF \ /7/
BL OF bltally @ 0= IF S" " ELSE S" " THEN out
1 bltally +! ENDOF
I 1 out 0 bltally !
ENDCASE LOOP ;
: outhattr ( a n -- ) \ HTMLized text output
BOUNDS ?DO I C@ CASE
[CHAR] & OF S" amp" out ENDOF
[CHAR] < OF S" lt" out ENDOF
[CHAR] > OF S" gt" out ENDOF
[CHAR] " OF S" quot" out ENDOF
[CHAR] + OF S" plus" out ENDOF
[CHAR] ! OF S" bang" out ENDOF
[CHAR] / OF S" slash" out ENDOF
[CHAR] \ OF S" backslash" out ENDOF
[CHAR] ' OF S" apos" out ENDOF
[CHAR] = OF S" equal" out ENDOF
[CHAR] - OF S" dash" out ENDOF
[CHAR] @ OF S" at" out ENDOF
[CHAR] ; OF S" semi" out ENDOF
[CHAR] * OF S" star" out ENDOF
[CHAR] ? OF S" question" out ENDOF
[CHAR] ~ OF S" tilde" out ENDOF
[CHAR] # OF S" pound" out ENDOF
[CHAR] , OF S" comma" out ENDOF
[CHAR] $ OF S" dollar" out ENDOF
[CHAR] | OF S" bar" out ENDOF
[CHAR] [ OF S" ltbracket" out ENDOF
[CHAR] ( OF S" ltparen" out ENDOF
[CHAR] { OF S" ltbrace" out ENDOF
[CHAR] ] OF S" rtbracket" out ENDOF
[CHAR] ) OF S" rtparen" out ENDOF
[CHAR] } OF S" rtbrace" out ENDOF
BL OF S" _" out ENDOF
I 1 out
ENDCASE LOOP ;
: end_header ( -- ) \ output end of HTML file /7/
end_header_part1 boiler
copyright ?DUP IF outh ELSE drop THEN
end_header_part2 boiler ;
: label ( addr len -- ) /a \ associate a label with a word
misctext 1 line outhattr
"> 1 attrib ! ;
\ Assuming this is running on a PC, we allocate enough storage that crashes from
\ string overflows can't happen.
CREATE inbuf 260 CHARS ALLOT \ current line from file
CREATE token 260 CHARS ALLOT \ last blank delimited string
CREATE XPAD 260 CHARS ALLOT \ temporary pad for word storage
CREATE EPAD 260 CHARS ALLOT \ temporary pad for evaluation
CREATE fn 260 CHARS ALLOT \ file name
CREATE fn1 260 CHARS ALLOT \ file name backup
CREATE fn2 260 CHARS ALLOT \ global file name
CREATE fn3 260 CHARS ALLOT \ index file name
CREATE "str" 260 CHARS ALLOT \ parsed string storage
CREATE uname 260 CHARS ALLOT \ : definition name
0 VALUE inf
VARIABLE nufile \ T if nesting a file
VARIABLE utype \ type of defined word
VARIABLE hstate
VARIABLE linenum
VARIABLE special \ special action, 0=none
\ Defining word for hyperlinks to words in HTML standards files.
: std ( word 2nd_fn color filename label -- )
CREATE ' , ' , BL WORD COUNT ,$ BL WORD COUNT ,$
DOES> /a DUP >R 2 CELLS +
R out S" #" out \ output file name
R> COUNT out "> \ and anchor name
1 attrib !
R> 2@ SWAP EXECUTE EXECUTE ; \ extra attributes
: genHTML ( -- ) \ generate pending HTML
token COUNT DUP IF setcolor THEN outh closefont /a 0 token ! ;
: isnumber? ( addr len -- f ) \ string converts to number?
0 0 2SWAP >NUMBER NIP NIP NIP 0= ;
: hparse ( a len char -- a' len' )
>R 2DUP R@ SKIP R> SCAN BL SCAN
2SWAP 2 PICK - token +PLACE ;
: >XPAD ( -- ) token COUNT BL SKIP XPAD PLACE ; \ move to temporary pad
: hint ( addr len -- ) \ interpret one line...
BEGIN
0 token ! BL hparse token C@
WHILE unknown \ default color
>XPAD XPAD COUNT superlinks SEARCH-WORDLIST 0= \ fixed hyperlink?
IF XPAD COUNT hyperlinks SEARCH-WORDLIST \ got a hyperlink for this?
ELSE TRUE
THEN
IF DEPTH >R EXECUTE
R> DEPTH <> ABORT" stack depth change in HTML generator"
ELSE
XPAD COUNT BASE @ 10 = IF
>FLOAT IF FDROP numeric THEN \ valid float or integer
ELSE
isnumber? IF numeric THEN
THEN
THEN genHTML
REPEAT 2DROP newline ;
: shortname ( -- )
fn COUNT 2DUP [CHAR] . SCAN NIP - EPAD PLACE ;
: ofn ( -- addr len ) \ output file name
shortname S" .htm" EPAD +PLACE EPAD COUNT ;
: mfn ( -- addr len ) \ main file name
shortname S" _f.htm" EPAD +PLACE EPAD COUNT ;
: hcreate ( addr len -- )
DUP 0= IF 2DROP S" fakename" THEN \ in case the name is missing
S" CREATE " EPAD PLACE EPAD +PLACE
GET-CURRENT >R hyperlinks SET-CURRENT
EPAD COUNT EVALUATE R> SET-CURRENT ; \ create a hyperlink generator
\ The user defined words use the following data structure:
\ CELL xt of coloring word
\ CELL pointer to index \ /9/
\ STRING name of reference word
\ STRING name of file
\ An index is a list of all of the words that call a defined word. The master
\ index is a list of indexes.
variable index 0 index ! \ index is a SLL of SLLs.
variable ulast 0 ulast ! \ last compiled definition name
variable tally 0 tally ! \ tally of defined words
\ Each index entry consists of a pointer to the previous index entry and a SLL.
\ The SLL (single linked list) starts out empty so upon creation it is 0.
\ index --> link_to_next
\ link_to_usedby_list <-- 'index
\ link to defined name structure
\ tally
: ( n -- ) \ create link to index n
frames if \ links undesirable if no frames
1 attrib ! \ or for W3C HTML validation
else drop then ;
: newindex ( -- 'index ) \ add an index to the list
HERE index @ , HERE SWAP 0 , index ! 0 , \ /9/
1 tally +! tally @ , ;
\ 'index -> link_to_next
\ link to usedby name structure
: newlink ( 'index -- 'index ) \ add a name to the index /9/
dup @ @ ?dup if \ non-empty sublist?
cell+ @ ulast @ = if exit then \ duplicate entry
then
HERE OVER @ @ , ulast @ , OVER @ ! ;
variable indexing
: deflink ( addr -- ) \ defined word makes hyperlink
DUP @ EXECUTE CELL+ \ set color
ulast @ IF newlink THEN CELL+ \ /9/ optional addition to index
DUP COUNT + COUNT ofn COMPARE \ in an external file?
IF R dup deflink
R@ if S" " out then
2 cells + count outh
R> if S" " out then
/a S" " out ;
: outdex ( sll -- ) \ output list of client words
0 ulast !
begin ?dup while dup cell+ @ 0 showlink @ repeat ;
: iname ( a -- ) \ display index root name
@ 1 showlink ;
: defx ( a len xt -- a' len' )
newindex >R
>R genHTML BL hparse >XPAD \ output defining word
XPAD COUNT 2DUP hcreate R> HERE SWAP ,
R@ CELL+ ! \ resolve link to definition name
R> , ,$ ofn ,$ \ rest of structure
DOES> deflink ;
: labelnow XPAD COUNT label /a tally @ genHTML /a ;
: defdat ['] numeric defx numeric labelnow ;
: defvar ['] variables defx variables labelnow ;
: defusr ['] userwords defx userwords labelnow ;
: defval ['] values defx values labelnow ;
: defdef ['] userdefiner defx userdefiner labelnow ;
: hstate=0 ( -- ) 0 hstate ! ;
: hstate=1 ( -- ) 1 hstate ! ;
: spec=zero ( -- ) 1 special ! ;
: NONE ( -- ) 0 special ! ; \ plain word
: skip) ( a len -- a' len' ) [CHAR] ) hparse ;
: skip} ( a len -- a' len' ) [CHAR] } hparse ; \ /7/
: skipw ( a len -- a' len' ) BL hparse ;
: skipc ( a len -- a len ) hstate @ 0= IF numeric skipw THEN ;
: skip" ( a len -- a' len' ) \ copy string to "str"
genHTML [CHAR] " hparse token COUNT 1- 0 MAX "str" PLACE ; \ /10/
\ ------------------------------------------------------------------------------
\ ":" definitions might be defining words, so they can't be assumed to be defusr
\ types. ":" makes a label and saves the name for later use by ";" which makes
\ a hyperlink or a hyperlink defining word.
:NONAME \ normal : definition
ulast @ if \ ending a : definition?
uname COUNT ['] userwords defx 2DROP 0 token !
then
; CONSTANT normal_def
:NONAME
newindex >R
uname COUNT 2DUP hcreate HERE ['] userwords , R@ CELL+ ! R> , ,$ ofn ,$
DOES> deflink defdef
; CONSTANT defining_def
\ ULAST points to a data structure containing the hyperlink to the word being
\ defined. It is used when building the index because it can't wait until ;
\ resolves the definition before requiring the hyperlink.
: defunk ( a len -- a' len' ) \ starting unknown definition
hstate=1 normal_def utype ! \ save name of : definition
genHTML skipw userwords token COUNT BL SKIP
2DUP label /a tally @ 1+ \ link to index
2DUP HERE ulast ! ['] NOOP , PAD , ,$ ofn ,$ \ save name of definition /9/
uname PLACE ;
: resunk ( -- ) \ resolve unknown defined word
genHTML utype @ EXECUTE hstate=0
0 ulast ! ; \ indexing off
: created ( -- ) hstate @
IF defining_def utype ! \ make ; create a defining word
ELSE defdat \ not compiling
THEN ;
\ ------------------------------------------------------------------------------
: header ( -- ) \ output big header text /8/9/
fn count
misctext 6 line
linksource
IF
outln misctext 9 line
ELSE outln
THEN misctext 7 line ;
: _incfil ( addr -- ) \ trigger file nesting /4/
nestable 0= IF DROP EXIT THEN \ don't nest files if disabled
COUNT BL SKIP 2DUP R/O OPEN-FILE \ can the file be opened?
IF DROP 2DROP \ no
ELSE CLOSE-FILE DROP \ yes
fn COUNT fn1 PLACE fn PLACE 1 nufile !
THEN ;
: incfile ( a len -- a' len' ) \ include a file
genHTML skipw token _incfil ;
: "incfil ( a len -- a' len' ) \ include file from S" filename"
skipw "str" _incfil ;
: hfill ( -- len ior ) \ read next line of file
inbuf 256 BL FILL
XPAD 256 inf READ-LINE ABORT" Error reading file"
>R >R 0 XPAD R> BOUNDS ( idx . . )
?DO I C@ 9 = IF 3 RSHIFT 1+ 3 LSHIFT \ tab
ELSE I C@ OVER 255 AND CHARS inbuf + C!
1+ DUP 256 = IF CR ." Input line too long" THEN
THEN
LOOP R>
1 linenum +! ;
\ OPEN and CLOSE input files
: open ( -- ) CR ." Reading " fn COUNT TYPE ." at line " linenum @ decimal .
0 linenum !
fn COUNT R/O OPEN-FILE ABORT" Error opening source file" TO inf ;
: close ( -- ) CR ." closing " fn COUNT TYPE
inf CLOSE-FILE ABORT" Error closing file" ;
: .title ( addr len -- ) \ output as title string
BOUNDS ?DO I C@ BL = IF S" %20" out ELSE I 1 out THEN LOOP ;
\ OPEN and CLOSE output files
: ocreate ( addr len -- )
W/O CREATE-FILE ABORT" Error creating file" TO outf ;
: oopen ( -- ) \ create new output file
ofn ocreate begin_header boiler \ begin boilerplate
fn COUNT .title mid_header boiler \ title and end boilerplate
bold IF S" " out THEN ;
: fclose ( -- )
outf CLOSE-FILE ABORT" Error closing file" ;
: new-output ( -- ) \ start a new output file /9/
open oopen header S" " out ;
\ Create index and frame files
: make-index ( -- ) \ make index /9/
cr ." building index "
1 indexing !
fn2 count fn place
cr ." Framed version: " mfn type
mfn ocreate \ create the main file
misctext 10 line fn count .title
ofn xpad place
fn3 COUNT fn PLACE \ filename for index
misctext 11 line xcr misctext 12 line xcr \