/
ColdCore-3.0a8/
ColdCore-3.0a8/src/
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;
};