1implement Fmt; 2 3# 4# Copyright © 2002 Lucent Technologies Inc. 5# based on the Plan 9 command; subject to the Lucent Public License 1.02 6# this Vita Nuova variant uses Limbo channels and processes to avoid accumulating words 7# 8 9# 10# block up paragraphs, possibly with indentation 11# 12 13include "sys.m"; 14 sys: Sys; 15 16include "draw.m"; 17 18include "bufio.m"; 19 bufio: Bufio; 20 Iobuf: import bufio; 21 22include "arg.m"; 23 24Fmt: module 25{ 26 init: fn(nil: ref Draw->Context, nil: list of string); 27}; 28 29extraindent := 0; # how many spaces to indent all lines 30indent := 0; # current value of indent, before extra indent 31length := 70; # how many columns per output line 32join := 1; # can lines be joined? 33maxtab := 8; 34bout: ref Iobuf; 35 36Word: adt { 37 text: string; 38 indent: int; 39 bol: int; 40}; 41 42init(nil: ref Draw->Context, args: list of string) 43{ 44 sys = load Sys Sys->PATH; 45 bufio = load Bufio Bufio->PATH; 46 arg := load Arg Arg->PATH; 47 48 arg->init(args); 49 arg->setusage("fmt [-j] [-i indent] [-l length] [file...]"); 50 while((c := arg->opt()) != 0) 51 case(c){ 52 'i' => 53 extraindent = int arg->earg(); 54 'j' => 55 join = 0; 56 'w' or 'l' => 57 length = int arg->earg(); 58 * => 59 arg->usage(); 60 } 61 args = arg->argv(); 62 if(length <= extraindent){ 63 sys->fprint(sys->fildes(2), "fmt: line length<=indentation\n"); 64 raise "fail:length"; 65 } 66 arg = nil; 67 68 err := ""; 69 bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE); 70 if(args == nil){ 71 bin := bufio->fopen(sys->fildes(0), Bufio->OREAD); 72 fmt(bin); 73 }else 74 for(; args != nil; args = tl args){ 75 bin := bufio->open(hd args, Bufio->OREAD); 76 if(bin == nil){ 77 sys->fprint(sys->fildes(2), "fmt: can't open %s: %r\n", hd args); 78 err = "open"; 79 }else{ 80 fmt(bin); 81 if(tl args != nil) 82 bout.putc('\n'); 83 } 84 } 85 bout.flush(); 86 if(err != nil) 87 raise "fail:"+err; 88} 89 90fmt(f: ref Iobuf) 91{ 92 words := chan of ref Word; 93 spawn parser(f, words); 94 printwords(words); 95} 96 97parser(f: ref Iobuf, words: chan of ref Word) 98{ 99 while((s := f.gets('\n')) != nil){ 100 if(s[len s-1] == '\n') 101 s = s[0:len s-1]; 102 parseline(s, words); 103 } 104 words <-= nil; 105} 106 107parseline(line: string, words: chan of ref Word) 108{ 109 ind: int; 110 (line, ind) = indentof(line); 111 indent = ind; 112 bol := 1; 113 for(i:=0; i < len line;){ 114 # find next word 115 if(line[i] == ' ' || line[i] == '\t'){ 116 i++; 117 continue; 118 } 119 # where does this word end? 120 for(l:=i; l < len line; l++) 121 if(line[l]==' ' || line[l]=='\t') 122 break; 123 words <-= ref Word(line[i:l], indent, bol); 124 bol = 0; 125 i = l; 126 } 127 if(bol) 128 words <-= ref Word("", -1, bol); 129} 130 131indentof(line: string): (string, int) 132{ 133 ind := 0; 134 for(i:=0; i < len line; i++) 135 case line[i] { 136 ' ' => 137 ind++; 138 '\t' => 139 ind += maxtab; 140 ind -= ind%maxtab; 141 * => 142 return (line, ind); 143 } 144 # plain white space doesn't change the indent 145 return (line, indent); 146} 147 148printwords(words: chan of ref Word) 149{ 150 # one output line per loop 151 nw := <-words; 152 while((w := nw) != nil){ 153 # if it's a blank line, print it 154 if(w.indent == -1){ 155 bout.putc('\n'); 156 nw = <-words; 157 continue; 158 } 159 # emit leading indent 160 col := extraindent+w.indent; 161 printindent(col); 162 # emit words until overflow; always emit at least one word 163 for(n:=0;; n++){ 164 bout.puts(w.text); 165 col += len w.text; 166 if((nw = <-words) == nil) 167 break; # out of words 168 if(nw.indent != w.indent) 169 break; # indent change 170 nsp := nspaceafter(w.text); 171 if(col+nsp+len nw.text > extraindent+length) 172 break; # fold line 173 if(!join && nw.bol) 174 break; 175 for(j:=0; j<nsp; j++) 176 bout.putc(' '); # emit space; another word will follow 177 col += nsp; 178 w = nw; 179 } 180 bout.putc('\n'); 181 } 182} 183 184printindent(w: int) 185{ 186 while(w >= maxtab){ 187 bout.putc('\t'); 188 w -= maxtab; 189 } 190 while(--w >= 0) 191 bout.putc(' '); 192} 193 194# give extra space if word ends with punctuation 195nspaceafter(s: string): int 196{ 197 if(len s < 2) 198 return 1; 199 if(len s < 4 && s[0] >= 'A' && s[0] <= 'Z') 200 return 1; # assume it's a title, not full stop 201 if((c := s[len s-1]) == '.' || c == '!' || c == '?') 202 return 2; 203 return 1; 204} 205