new object $compiler: $utilities; var $compiler glue_table = ["p", "br", "ul", "ol", "dl", "table", "switch", "case", "range", "default"]; var $root created_on = 837583518; var $root flags = ['variables, 'methods, 'code, 'core]; var $root inited = 1; var $root managed = [$compiler]; var $root manager = $compiler; public method ._check_glue() { arg tokens, i; var j, bracket; // WARNING: This method can throw (it should be called with (| |)'s) while ((tokens[i]) == " ") i++; if ((bracket = tokens[i++]) in ["{", "["]) { while ((tokens[i]) == " ") i++; j = i; if ((tokens[i]) in glue_table) return [j, (bracket == "{") ? 'fmtname : 'genname]; } return 0; }; public method ._kill_spaces() { arg list; var i; return filter i in (list) where (i != " "); }; public method ._make_tag() { arg token, mode, vars, name, flags, args; var method, class; if (mode == 'fmtname) { if (token != "}") throw(~parse, "Extra ']' encountered."); method = tosym("do_" + (name.strip())); class = $format; } if (mode == 'genname) { if (token != "]") throw(~parse, "Extra '}' encountered."); method = tosym("gen_" + (name.strip())); class = $generator; } catch ~methodnf return .(method)(vars, flags, args); with return [class.new_tag(name, flags, args), vars]; }; public method .compile_cml() { arg text; var vars, ret; vars = #[]; (> (ret = .parse_string_new(vars, .tokenize(text))) <); return $ctext_frob.new_with(ret[1], ret[2]); }; public method .do_action() { arg vars, flags, args; var actions, item, link, scheme, i; if (listlen(args) == 0) throw(~parse, "{action} requires an argument of the name."); if (!dict_contains(flags, "cmd")) throw(~parse, "no cmd flag for {action}"); // nix everything else flags = #[["cmd", flags["cmd"]]]; return [$format.new_tag("link", flags, args), vars]; }; public method .do_detail() { arg vars, flags, args; var dets, item, name; dets = (| vars['details] |) || #[]; for item in (flags) { if ((type(item) == 'list) && ((item[1]) == "name")) { name = item[2]; break; } } if (!name) throw(~parse, "No name for {detail}."); dets = dets.add(name, args); return [$format.new_tag("detail", flags, []), vars.add('details, dets)]; }; public method .do_dl() { arg vars, flags, args; return [$format.new_tag("dl", flags, ._kill_spaces(args)), vars]; }; public method .do_link() { arg vars, flags, args; var links, item, node; if (listlen(args) == 0) throw(~parse, "{link} requires an argument of the link name."); links = (| vars['links] |) || #[]; for item in (flags) { if ((type(item) == 'list) && ((item[1]) == "node")) { node = item[2]; break; } } if (!node) throw(~parse, "No node for {link}."); if (((args.length()) != 1) || (type(args[1]) != 'string)) throw(~parse, "{link} argument must be a string"); links = links.add(args[1], node); return [$format.new_tag("link", flags, args), vars.add('links, links)]; }; public method .do_method() { arg vars, flags, args; var m; if (!(m = (| ((flags.to_list())[1])[1] |))) throw(~parse, "{method} syntax: {method OBJ.METHOD:text}"); if ((m[1]) != "$") m = "$" + m; m = (> $parse_lib.ref(m) <); if ((m[1]) != 'method) throw(~parse, "{method} target is not a method reference"); m = (substr(tostr(m[2]), 2) + ".") + (m[4]); return [$format.new_tag("method", [m], args), vars]; }; public method .do_obj() { arg vars, flags, args; if (!dict_contains(flags, "context")) throw(~parse, "no context flag for {obj}"); if (!dict_contains(flags, "object")) throw(~parse, "no object flag for {obj}"); if (listlen(args) == 0) throw(~parse, "{obj} requires an arg of the name"); flags = flags.replace("context", tosym(flags["context"])); return [$format.new_tag("obj", flags, args), vars]; }; public method .do_ol() { arg vars, flags, args; return [$format.new_tag("ol", flags, ._kill_spaces(args)), vars]; }; public method .do_switch() { arg vars, flags, args; var current, d, tag, i, m, r; if (listlen(args) == 0) throw(~parse, "[switch] requires arguments."); if (!(| flags["value"] |)) throw(~parse, "Value flag missing."); d = #[]; r = []; tag = (current = 0); // The last default is a dummy - it forces the parser to flush current for i in (args + [$generator.new_tag("default", [], [])]) { if ((type(i) == 'frob) && ((class(i) == $generator) && ((i.name()) in ["case", "default", "range"]))) { if (tag != 0) { if (type(tag) == 'list) r += [[@tag.subrange(2), current]]; else d = d.add(tag, current); } current = []; switch (i.name()) { case "case": if ((((i.args()).length()) != 1) || (type((tag = (i.args())[1])) != 'string)) throw(~parse, "[case:...] takes only a single string as an argument."); case "range": if ((((i.args()).length()) != 1) || (!(| ((m = ((i.args())[1]).match_pattern("*..*")).length()) == 2 |))) throw(~parse, "range tag should look like [range:lower..upper]."); tag = ['range, @m]; case "default": tag = 'default; } } else { if (tag == 0) throw(~parse, "Expression before [case]"); current += [i]; } } if (r) d = d.add('ranges, r); return [$format.new_tag("switch", flags, d), vars]; }; public method .do_table() { arg vars, flags, args; return [$format.new_tag("table", flags, ._kill_spaces(args)), vars]; }; public method .do_tr() { arg vars, flags, args; return [$format.new_tag("tr", flags, ._kill_spaces(args)), vars]; }; public method .do_ul() { arg vars, flags, args; return [$format.new_tag("ul", flags, ._kill_spaces(args)), vars]; }; public method .gen_data() { arg vars, flags, args; if (!(flags.contains("source"))) throw(~parse, "no source flag for [data]"); return [$generator.new_tag("data", flags, args), vars]; }; public method .gen_switch() { arg vars, flags, args; var current, d, tag, i, m, r; if (listlen(args) == 0) throw(~parse, "[switch] requires arguments."); if (!(| flags["value"] |)) throw(~parse, "Value flag missing."); d = #[]; r = []; tag = (current = 0); // The last default is a dummy - it forces the parser to flush current for i in (args + [$generator.new_tag("default", [], [])]) { if ((type(i) == 'frob) && ((class(i) == $generator) && ((i.name()) in ["case", "default", "range"]))) { if (tag != 0) { if (type(tag) == 'list) r += [[@tag.subrange(2), current]]; else d = d.add(tag, current); } current = []; switch (i.name()) { case "case": if ((((i.args()).length()) != 1) || (type((tag = (i.args())[1])) != 'string)) throw(~parse, "[case:...] takes only a single string as an argument."); case "range": if ((((i.args()).length()) != 1) || (!(| ((m = ((i.args())[1]).match_pattern("*..*")).length()) == 2 |))) throw(~parse, "range tag should look like [range:lower..upper]."); tag = ['range, @m]; case "default": tag = 'default; } } else { if (tag == 0) throw(~parse, "Expression before [case]"); current += [i]; } } if (r) d = d.add('ranges, r); return [$generator.new_tag("switch", flags, d), vars]; }; public method .glue_table() { return glue_table; }; public method .parse_string_new() { arg vars, tokens; var mode, out, word, token, i, tmp, ret; if (!tokens) return [[], vars]; mode = ['spaces, 'plain]; i = 1; out = []; word = ""; while (i <= (tokens.length())) { refresh(); token = tokens[i++]; switch (mode[1]) { case 'spaces: if (token != " ") { i--; mode = mode.subrange(2); } case 'plain: if (!(token in ["{", "["])) { if (token == " ") { // No scatter because .check can throw if ((| (ret = ._check_glue(tokens, i)) |)) { i = ret[1]; mode = [ret[2], @mode]; if (word) out += [word]; word = ""; continue; } } word += token.sed("\\\(.)", "%1", "g"); } else { if (word) out += [word]; word = ""; mode = ['spaces, (token == "{") ? 'fmtname : 'genname, @mode]; } case 'fmtname, 'genname: if (((mode[1]) == 'fmtname) && (token == "quote")) { mode = mode.subrange(2); out += [$format.new_tag("quote", [], [tokens[i++]])]; i++; } else { if (token in glue_table) mode = [mode[1], 'spaces, @mode.subrange(2)]; mode = ['spaces, 'flags, #[], token, @mode]; } case 'flags: if (token in ["}", "]"]) { ret = ._make_tag(token, mode[4], vars, mode[3], mode[2], []); vars = ret[2]; out += [ret[1]]; mode = mode.subrange(5); } else if (token == "=") { throw(~parse, "Value flag with no key."); } else if (token == ":") { mode = ['spaces, 'args, out, mode[2], @mode.subrange(3)]; out = []; } else if ((| (tokens[i]) == "=" |)) { mode = ['flagvalue, token, @mode]; i++; } else { mode = mode.replace(2, (mode[2]).add(token, 1)); } case 'flagvalue: if (token in ["}", "]", ":"]) { mode = mode.subrange(3); i--; } else if (token == " ") { mode = mode.subrange(3); } else if (!(token in ["[", "{"])) { mode = ['spaces, @(mode.subrange(3)).replace(2, (mode[4]).add(mode[2], token.sed("\\\(.)", "%1", "g")))]; } else { if (word) out += [word]; word = ""; mode = ['spaces, (token == "{") ? 'fmtname : 'genname, 'flagset, @mode.subrange(2)]; } case 'flagset: i--; mode = ['spaces, @(mode.subrange(3)).replace(2, (mode[4]).add(mode[2], out[out.length()]))]; out = out.subrange(1, (out.length()) - 1); case 'args: if (token in ["}", "]"]) { if (word) { out += [word]; word = ""; } ret = ._make_tag(token, mode[5], vars, mode[4], mode[3], out); vars = ret[2]; out = (mode[2]) + [ret[1]]; mode = mode.subrange(6); } else if (token in ["{", "["]) { if (word) out += [word]; word = ""; mode = ['spaces, (token == "{") ? 'fmtname : 'genname, @mode]; } else { if (token == " ") { if ((| (ret = ._check_glue(tokens, i)) |)) { i = ret[1]; mode = [ret[2], @mode]; if (word) out += [word]; word = ""; continue; } } word += token.sed("\\\(.)", "%1", "g"); } } } if (word) out += [word]; while ((mode[1]) == 'spaces) mode = mode.subrange(2); if (mode != ['plain]) throw(~parse, "Unclosed tag."); return [out, vars]; }; public method .tokenize() { arg text; var word, out, escaped, token, i, pre_count, open_form, str; // break text into a list of tokens. if (type(text) == 'string) text = [text]; out = []; word = ""; escaped = 0; // pre_count is 0, except inside pre, when it counts the {}'s pre_count = 0; open_form = 0; for str in (text) { str = str.explode(" ", 1); for token in (str) { refresh(); if ((!token) && (!pre_count)) continue; while ((i = token.match_regexp("[][{}=\:]"))) { refresh(); i = (i[1])[1]; if (escaped) { escaped = 0; word = (word + "\\") + (token.subrange(1, i)); } else if (pre_count) { if ((token[i]) == "{") { pre_count++; word += token.subrange(1, i); } else if ((token[i]) == "}") { pre_count--; if (pre_count) { word += token.subrange(1, i); } else { word += token.subrange(1, i - 1); out = word ? (out + [word, token[i]]) : (out + [token[i]]); word = ""; } } else { word += token.subrange(1, i); } } else { word += token.subrange(1, i - 1); open_form = (token[i]) == "{"; if ((token[i]) == "\\") { escaped = 1; } else { out = word ? (out + [word, token[i]]) : (out + [token[i]]); word = ""; } } token = token.subrange(i + 1); } if (open_form && (token == "quote")) { pre_count = 1; open_form = 0; out += [token]; token = ""; continue; } word += token; if (escaped || pre_count) { escaped = 0; word += " "; } else { out = word ? (out + [word, " "]) : (out + [" "]); word = ""; } } if (pre_count) word = word ? ((word.subrange(1, (word.length()) - 1)) + "\n") : "\n"; } if (word) out += [word]; if (out) { if ((out.last()) == " ") out = out.subrange(1, (out.length()) - 1); else out = out.replace(out.length(), (out.last()).subrange(((out.last()).length()) - 1)); } return out; };