more robust error handling (NB: Source.source requires total scanner or recover);
tuned;
1 (* Title: Pure/Thy/rail.ML
2 Author: Michael Kerscher, TU München
5 Railroad diagrams in LaTeX.
8 structure Rail: sig end =
11 (** lexical syntax **)
15 datatype kind = Keyword | Ident | String | EOF;
17 datatype token = Token of Position.range * (kind * string);
19 fun pos_of (Token ((pos, _), _)) = pos;
20 fun end_pos_of (Token ((_, pos), _)) = pos;
22 fun kind_of (Token (_, (k, _))) = k;
23 fun content_of (Token (_, (_, x))) = x;
29 fn Keyword => "rail keyword"
30 | Ident => "identifier"
31 | String => "single-quoted string"
32 | EOF => "end-of-file";
34 fun print (Token ((pos, _), (k, x))) =
35 (if k = EOF then print_kind k else print_kind k ^ " " ^ quote x) ^
38 fun print_keyword x = print_kind Keyword ^ " " ^ quote x;
43 fun mk_eof pos = Token ((pos, Position.none), (EOF, ""));
44 val eof = mk_eof Position.none;
46 fun is_eof (Token (_, (EOF, _))) = true
50 Scan.stopper (fn [] => eof | toks => mk_eof (end_pos_of (List.last toks))) is_eof;
57 fun token k ss = [Token (Symbol_Pos.range ss, (k, Symbol_Pos.content ss))];
59 val scan_space = Scan.many1 (Symbol.is_blank o Symbol_Pos.symbol);
62 Scan.one (member (op =) ["|", "*", "+", "?", "(", ")", "\\", ";", ":"] o Symbol_Pos.symbol);
66 scan_keyword >> (token Keyword o single) ||
67 Lexicon.scan_id >> token Ident ||
68 Symbol_Pos.scan_string_q >> (token String o #1 o #2);
71 (Scan.repeat scan_token >> flat) --|
72 Symbol_Pos.!!! "Rail lexical error: bad input"
73 (Scan.ahead (Scan.one Symbol_Pos.is_eof));
78 #1 (Scan.error (Scan.finite Symbol_Pos.stopper scan) (Symbol_Pos.explode text));
88 val prefix = "Rail syntax error";
90 fun get_pos [] = " (past end-of-file!)"
91 | get_pos (tok :: _) = Position.str_of (pos_of tok);
93 fun err (toks, NONE) = prefix ^ get_pos toks
94 | err (toks, SOME msg) =
95 if String.isPrefix prefix msg then msg
96 else prefix ^ get_pos toks ^ ": " ^ msg;
97 in Scan.!! err scan end;
100 Scan.one (fn tok => kind_of tok = Keyword andalso content_of tok = x) ||
102 (fn [] => print_keyword x ^ " expected (past end-of-file!)"
103 | tok :: _ => print_keyword x ^ " expected,\nbut " ^ print tok ^ " was found");
105 fun enum1 sep scan = scan ::: Scan.repeat ($$$ sep |-- !!! scan);
106 fun enum sep scan = enum1 sep scan || Scan.succeed [];
108 fun parse_token kind =
109 Scan.some (fn tok => if kind_of tok = kind then SOME (content_of tok) else NONE);
111 val ident = parse_token Ident;
112 val string = parse_token String;
116 (** rail expressions **)
121 Cat of int * rail list
124 Plus of rails * rails |
126 Nonterminal of string |
129 fun reverse_cat (Cat (y, rails)) = Cat (y, rev (map reverse rails))
130 and reverse (Bar cats) = Bar (map reverse_cat cats)
131 | reverse (Plus (cat1, cat2)) = Plus (reverse_cat cat1, reverse_cat cat2)
134 fun cat rails = Cat (0, rails);
137 fun is_empty (Cat (_, [])) = true | is_empty _ = false;
139 fun is_newline (Newline _) = true | is_newline _ = false;
141 fun bar [Cat (_, [rail])] = rail
142 | bar cats = Bar cats;
144 fun plus cat1 cat2 = Plus (cat1, reverse_cat cat2);
147 if is_empty cat2 then plus empty cat1
148 else bar [empty, cat [plus cat1 cat2]];
150 fun maybe rail = bar [empty, cat [rail]];
157 fun body x = (enum1 "|" body1 >> bar) x
158 and body0 x = (enum "|" body1 >> bar) x
161 $$$ "*" |-- !!! body4e >> (cat o single o star a) ||
162 $$$ "+" |-- !!! body4e >> (cat o single o plus a) ||
164 and body2 x = (Scan.repeat1 body3 >> cat) x
165 and body3 x = (body4 :|-- (fn a => $$$ "?" >> K (maybe a) || Scan.succeed a)) x
167 ($$$ "(" |-- !!! (body0 --| $$$ ")") ||
168 $$$ "\\" >> K (Newline 0) ||
169 ident >> Nonterminal ||
170 string >> Terminal) x
171 and body4e x = (Scan.option body4 >> (cat o the_list)) x;
173 val rule = ident -- ($$$ ":" |-- !!! body) || body >> pair "";
174 val rules = enum1 ";" (Scan.option rule) >> map_filter I;
179 (case Scan.error (Scan.finite stopper rules) (tokenize text) of
181 | (_, tok :: _) => error ("Malformed rail input: " ^ print tok));
190 fun vertical_range_cat (Cat (_, rails)) y =
191 let val (rails', (_, y')) =
192 fold_map (fn rail => fn (y0, y') =>
193 if is_newline rail then (Newline (y' + 1), (y' + 1, y' + 2))
195 let val (rail', y0') = vertical_range rail y0;
196 in (rail', (y0, Int.max (y0', y'))) end) rails (y, y + 1)
197 in (Cat (y, rails'), y') end
199 and vertical_range (Bar cats) y =
200 let val (cats', y') = fold_map vertical_range_cat cats y
201 in (Bar cats', Int.max (y + 1, y')) end
202 | vertical_range (Plus (cat1, cat2)) y =
203 let val ([cat1', cat2'], y') = fold_map vertical_range_cat [cat1, cat2] y;
204 in (Plus (cat1', cat2'), Int.max (y + 1, y')) end
205 | vertical_range (Newline _) y = (Newline (y + 2), y + 3)
206 | vertical_range atom y = (atom, y + 1);
209 fun output_text s = "\\isa{" ^ Output.output s ^ "}";
211 fun output_cat c (Cat (_, rails)) = outputs c rails
212 and outputs c [rail] = output c rail
213 | outputs _ rails = implode (map (output "") rails)
214 and output _ (Bar []) = ""
215 | output c (Bar [cat]) = output_cat c cat
216 | output _ (Bar (cat :: cats)) =
217 "\\rail@bar\n" ^ output_cat "" cat ^
218 implode (map (fn Cat (y, rails) =>
219 "\\rail@nextbar{" ^ string_of_int y ^ "}\n" ^ outputs "" rails) cats) ^
221 | output c (Plus (cat, Cat (y, rails))) =
222 "\\rail@plus\n" ^ output_cat c cat ^
223 "\\rail@nextplus{" ^ string_of_int y ^ "}\n" ^ outputs "c" rails ^
225 | output _ (Newline y) = "\\rail@cr{" ^ string_of_int y ^ "}\n"
226 | output c (Nonterminal s) = "\\rail@" ^ c ^ "nont{" ^ output_text s ^ "}[]\n"
227 | output c (Terminal s) = "\\rail@" ^ c ^ "term{" ^ output_text s ^ "}[]\n";
229 fun output_rule (name, rail) =
230 let val (rail', y') = vertical_range rail 0 in
231 "\\rail@begin{" ^ string_of_int y' ^ "}{" ^ output_text name ^ "}\n" ^
236 fun output_rules rules =
237 "\\begin{railoutput}\n" ^
238 implode (map output_rule rules) ^
239 "\\end{railoutput}\n";
244 Thy_Output.antiquotation "rail" (Scan.lift (Parse.position Args.name))
245 (fn _ => output_rules o read);