/*

    Minimum Profit 5.x
    A Programmer's Text Editor

    Syntax highlight definitions.

    Copyright (C) 1991-2013 Angel Ortega <angel@triptico.com>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    http://triptico.com

*/

/** editor actions **/

sub mp_doc.actions.help(d) {
    local w, h, r;

    /* get the special word regex separator for help (if any) */
    r = d.syntax.help_word_regex;

    /* if there is a word at the cursor position,
        try searching for help */
    if ((w = d->get_word(r)) != NULL) {
        d->busy(1);
        h = d->help(w);
        d->busy(0);
    }

    if (h == NULL)
        mp.alert(sprintf(L("No help for '%s'."), w));
    else {
        local hd;

        hd = mp.open(sprintf(L("<help on '%s'>"), w));

        hd.txt.lines    = h;
        hd.txt.y        = 0;
        hd.read_only    = 1;

        hd.syntax = d.syntax;
    }
}

sub mp_doc.actions.section_list(d) {
    local l = d->section_list();

    if (l == NULL)
        mp.alert(L("No detection for sections for this mode."));
    else {
        if (size(l) == 0)
            mp.alert(L("No sections were found in this document."));
        else {
            local pos = -1;

            /* set pos to the section nearest the cursor */
            foreach (e, l) {
                if (e[1] > d.txt.y)
                    break;

                pos++;
            }

            if (pos < 0 || pos > size(l))
                pos = 0;

            local r = mp.form(
                [
                    {
                        label:  L("Section list"),
                        type:   'list',
                        list:   map(l, sub (e) { e[0]; }),
                        value:  pos
                    }
                ]
            );

            if (r != NULL) {
                d->search_set_y(l[r[0]][1])->set_x(0);
            }
        }
    }
}


sub mp_doc.actions.move_section_up(d) {
    local pos = -1;

    foreach (e, d->section_list()) {
        if (e[1] >= d.txt.y)
            break;

        pos = e[1];
    }

    if (pos != -1) {
        d.txt.x = 0;
        d.txt.y = pos;
    }

    return d;
}


sub mp_doc.actions.move_section_down(d) {
    local pos = d.txt.y;

    foreach (e, d->section_list()) {
        if (e[1] > pos) {
            pos = e[1];
            break;
        }
    }

    d.txt.x = 0;
    d.txt.y = pos;

    return d;
}


/** default key bindings **/

mp_doc.keycodes['f1']               = "help";
mp_doc.keycodes['ctrl-d']           = "section_list";
mp_doc.keycodes['ctrl-cursor-up']   = "move_section_up";
mp_doc.keycodes['ctrl-cursor-down'] = "move_section_down";

/** action descriptions **/

mp.actdesc['help']              = LL("Help on word over cursor");
mp.actdesc['section_list']      = LL("Section list...");
mp.actdesc['move_section_up']   = LL("Move one section up");
mp.actdesc['move_section_down'] = LL("Move one section down");

/** data **/

mp.syntax = {};

/** syntax definitions **/

sub mp.syn_token_list(l) { '/\<(' ~ join(l, "|") ~ ')\>/'; }

mp.syntax.c = {
    id:         'c',
    name:       'C / C++',
    filenames:  [
        '/\.c$/i', '/\.h$/i', '/\.l$/i', '/\.y$/i', '/\.d$/i', '/\.hh$/i',
        '/\.cpp$/i', '/\.hpp$/i', '/\.c++$/i', '/\.cxx$/i', '/\.xpm$/i'
    ],
    help:       [ "man 2 %s", "man 3 %s" ],
    help_func:  sub (w) { mp.c_gather_help(w); },
    defs:       [
        'word1', [
            mp.syn_token_list(
                [
                    "auto", "break", "case", "catch", "class", "const", "const_cast",
                    "continue", "default", "delete", "do", "dynamic_cast", "else", "enum",
                    "explicit", "extern", "for", "friend", "goto", "if", "inline", "mutable",
                    "namespace", "new", "operator", "private", "protected", "public",
                    "register", "reinterpret_cast", "restrict", "return", "sizeof", "static",
                    "static_cast", "struct", "switch", "template", "this", "throw", "try",
                    "typedef", "typeid", "typename", "union", "using", "virtual", "volatile",
                    "while", "not", "not_eq", "xor", "xor_eq", "or",  "or_eq", "and", "and_eq",
                    "bitor", "bitand", "compl"
                ]
            )
        ],
        'word2', [
            mp.syn_token_list(
                [
                    "bool", "char", "double", "float", "int", "long", "short", "signed",
                    "unsigned", "wchar_t", "size_t", "ssize_t", "off_t", "wchar_t",
                    "ptrdiff_t", "void", "sig_atomic_t", "fpos_t", "clock_t", "time_t",
                    "va_list", "jmp_buf", "FILE", "DIR", "div_t", "ldiv_t", "mbstate_t",
                    "wctrans_t", "wint_t", "wctype_t", "complex", "int8_t", "int16_t",
                    "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t",
                    "int_least8_t", "int_least16_t", "int_least32_t", "int_least64_t",
                    "uint_least8_t", "uint_least16_t", "uint_least32_t", "uint_least64_t",
                    "int_fast8_t", "int_fast16_t", "int_fast32_t", "int_fast64_t",
                    "uint_fast8_t", "uint_fast16_t", "uint_fast32_t", "uint_fast64_t",
                    "intptr_t", "uintptr_t", "intmax_t", "uintmax_t"
                ]
            )
        ],
        'word3', [
            '/[-=<>:\?\+\*\/\!\%&\|~\^\.\{\}\(\)\[]+/',
            '/]/'
        ],
        'quotes', [
            /* from http://ad.hominem.org/log/2005/05/quoted_strings.php */
            "/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/", /* double-quoted strings */
            "/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/",     /* single-quoted strings */
            "/\<-?[0-9]+\>/",                           /* numbers */
            "/\<[0-9]+e[0-9]+\>/",                      /* numbers in exp format */
            "/\<0[0-7]+\>/",                            /* octal numbers */
            "/\<0x[0-9a-f]+\>/i",                       /* hex numbers */
            "/\<__(DATE|FILE|LINE|STDC|TIME|STDC_HOSTED|STDC_VERSION|func)__\>/", /* ISO macros */
            "/\<(true|false|NULL)\>/"                   /* symbolic constant names */
        ],
        'comments', [
            [ '|/\*|', '|\*/|' ],                       /* C-like */
            [ '|#if 0|', '#endif' ],                    /* CPP "comments" */
            '|//.*$|m',                                 /* C++ */
            '/^\s*#\s*[a-z]+/m'                         /* CPP directives */
        ],
        'documentation', [
            [ "|/\*\*\n|", '|\*/|' ],                   /* mp_doccer */
            '/^/\*\*.*\*\*/$/m'                         /* section mark */
        ]
    ],
    section:    [ '/(^\/\*\*.*\*\*\/$|^#pragma mark|^[A-Za-z_])/' ],
    build_targets: [
        "cc -c %s"
    ]
};

mp.syntax.mpsl = {
    id:         'mpsl',
    name:       'MPSL',
    filenames:  [ '/\.mpsl?$/i' ],
    defs:       [
        'word1', [
            mp.syn_token_list(
                [
                    'if', 'else', 'while', 'foreach', 'for',
                    'sub', 'break', 'return', 'eq', 'ne'
                ]
            ),
            mp.syn_token_list(keys(MPSL.CORE))
        ],
        'word2', [
            "/(NULL|local|global|this)/"
        ],
        'word3', [
            '/]/',
            '/[\.-=<>\?\+\*\/\!\%&\|\{\}\[]+/'
        ],
        'quotes', [
            /* from http://ad.hominem.org/log/2005/05/quoted_strings.php */
            "/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/", /* double-quoted strings */
            "/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/",     /* single-quoted strings */
            "/\<-?[0-9]+\>/",                           /* numbers */
            "/\<0x[0-9a-f]+\>/i",                       /* hex numbers */
            "/\<0b[0-1]+\>/i",                          /* binary numbers */
            '/\w+\s*:/',                                /* JS-type hashes */
            "/\<[0-9[:upper:]_]+\>/"                    /* all-caps words */
        ],
        'word3',    [
            '/:/'
        ],
        'comments', [
            [ '|/\*|', '|\*/|' ]                        /* C-like */
        ],
        'documentation', [
            [ "|/\*\*\n|", '|\*/|' ],                   /* mp_doccer */
            '/^/\*\*.*\*\*/$/m'                         /* section mark */
        ]
    ],
    detect: sub (d) {
        /* take the first line */
        local f = d.txt.lines[0];

        return regex(f, '|^#!\s*/usr/(local/)?bin/(env )?mpsl|');
    },
    section:    [ '/(^[A-Za-z_]|^\/\*\*.*\*\*\/$)/' ]
};

mp.syntax.nh3 = {
    id:         'nh3',
    name:       'nh3',
    filenames:  [ '/\.nh3?$/i' ],
    defs:       [
        'word1', [
            mp.syn_token_list(
                [
                    'if', 'else', 'while', 'foreach',
                    'sub', 'break', 'return', 'new'
                ]
            )
        ],
        'word2', [
            "/(NULL|var|this)/"
        ],
        'word3', [
            '/]/',
            '/[\.-=<>\?\+\*\/\!\%&\|\{\}\[]+/'
        ],
        'quotes', [
            /* from http://ad.hominem.org/log/2005/05/quoted_strings.php */
            "/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/", /* double-quoted strings */
            "/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/",     /* single-quoted strings */
            "/\<-?[0-9]+\>/",                           /* numbers */
            "/\<0x[0-9a-f]+\>/i",                       /* hex numbers */
            "/\<0b[0-1]+\>/i",                          /* binary numbers */
            '/\w+\s*:/',                                /* JS-type hashes */
            "/\<[0-9[:upper:]_]+\>/"                    /* all-caps words */
        ],
        'word3',    [
            '/:/'
        ],
        'comments', [
            '|//.*$|m',                                 /* C++ */
            [ '|/\*|', '|\*/|' ]                        /* C-like */
        ],
        'documentation', [
            [ "|/\*\*\n|", '|\*/|' ],                   /* mp_doccer */
            '/^/\*\*.*\*\*/$/m'                         /* section mark */
        ]
    ],
    detect: sub (d) {
        /* take the first line */
        local f = d.txt.lines[0];

        return regex(f, '|^#!.*nh3|');
    },
    section:    [ '/(^[A-Za-z_]|^\/\*\*.*\*\*\/$)/' ],
    build_targets: [
        "nh3 %s",
        "nh3 -t %s"
    ]
};

mp.syntax.rc = {
	'id'		=>	'rc',
	'name'		=>	'Resource file',
	'filenames'	=>	[ '/\.rc$/i' ],
	'defs'		=>	[
		'word1',	[ ],
		'word2',	[ ],
		'quotes',	[
			/* from http://ad.hominem.org/log/2005/05/quoted_strings.php */
			"/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/",		/* single-quoted strings */
			"/\<-?[0-9]+\>/",			/* numbers */
			"/\<0x[0-9a-f]+\>/i",			/* hex numbers */
			"/\<[0-9[:upper:]_]+\>/"		/* all-caps words */
		],
		'comments',	[
			[ '|/\*|', '|\*/|' ],			/* C-like */
			'|//.*$|m',				/* C++ */
			'/^\s*#[a-z]+/m'			/* CPP directives */
		]
	]
};

mp.syntax.perl = {
	'id'		=>	'perl',
	'name'		=>	'Perl',
	'filenames'	=>	[ '/\.pl$/i', '/\.pm$/i' ],
	'help'		=>	[ 'perldoc -f %s', 'perldoc %s' ],
	'help_word_regex' =>	'/[A-Z_][A-Z0-9_:]*/i',
	'defs'	=>	[
		'word1',	[
				mp.syn_token_list( [
				"for", "if", "next", "last", "else", "elsif",
				"unless", "while", "shift", "unshift", "push",
				"pop", "delete", "new", "bless", "return",
				"foreach", "keys", "values", "sort", "grep",
				"tr", "length", "system", "exec", "fork", "map",
				"print", "write", "open", "close", "chop",
				"chomp", "exit", "sleep", "split", "join",
				"sub", "printf", "sprintf", "s", "glob",
				"scalar", "my", "local", "undef", "defined",
				"use", "package", "require", "ref", "can", "isa",
				"qw", "qq", "eq", "ne", "or", "exists",
				"and", "not", "import", "our", "caller" ]),
				'/->/'
		],
		'word2',	[
				'/[:\?\+\*\/\!\$@\%&\|~\.]+/',
				'/[\$@%]\w+/'
		],
		'quotes',	[
			/* from http://ad.hominem.org/log/2005/05/quoted_strings.php */
			"/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/",		/* single-quoted strings */
			"/\<-?[0-9]+\>/",			/* numbers */
			"/\<0x[0-9a-f]+\>/i",			/* hex numbers */
			"/\<[0-9[:upper:]_]+\>/",		/* all-caps words */
			[ "/q\(/", "/\)/" ],			/* quote */
			[ "/qw\(/", "/\)/" ],			/* quote word */
			'/\w+\s*=>/',				/* barewords as hash keys 1 */
			'/\{\s*-?\w+\s*\}/',			/* barewords as hash keys 2 */
			[ "/`/", "/`/" ],			/* backticks */
			[ "/<<[\"']?EOF.*$/m", "/^EOF$/m" ]	/* 'document here' */
		],
		/* color all => as word2 */
		'word2',	[ '/=>/' ],
		/* color curly brackets as word */
		'word',		[ '/[{}]/' ],
		'comments',	[
			"/#.*$/m"				/* Comments */
		],
		'documentation',	[
			"/__END__\n.*$/",			/* __END__ */
			'/^## .*$/m',				/* section mark */
			[ "/^=(head[1-4]|over|item|back|pod|begin|end|for)/m",
				"/^=cut$/m" ]			/* POD */
		]
	],
	'detect'	=>	sub (d) {
		/* take the first line */
		local f = d.txt.lines[0];

		/* is it a 'she-bang' for Perl? */
		return regex(f, '/^#!\s*/usr/bin/(env )?perl/');
	},
	'section'	=>	[ '/(^sub \w+|^package|^## )/' ],
    'build_targets' => [
        'perl %s',
        'perl -cw %s'
    ]
};


mp.syntax.sh = {
	'id'		=>	'sh',
	'name'		=>	'Shell script',
	'filenames'	=>	[ '/\.sh$/i', '/makefile/i' ],
	'help'		=>	[ "man %s" ],
	'defs'		=>	[
		'word1',	[
				mp.syn_token_list( [
				"if", "then", "else", "elif",
				"fi", "case", "do", "done", "esac",
				"for", "until", "while", "break",
				"in", "source", "alias", "cd",
				"continue", "echo", "eval", "exec",
				"exit", "export", "kill", "logout",
				"printf", "pwd", "read", "return",
				"shift", "test", "trap", "ulimit",
				"umask", "unset", "wait", "cp", "rm" ]),
				'/[\{\}]/'

		],
		'word2',	[
				'/\<(local|let|set)\>/',
				'/[-=<>:\?\+\*\!\%&\|]+/',
				'/\$\w+/',
				'/\$\{\w+\}/',
				"/\<[0-9[:upper:]_]+\>/"	/* all-caps words */
		],
		'quotes',	[
			"/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/",		/* single-quoted strings */
			"/\<-?[0-9]+\>/",			/* numbers */
			"/\<0x[0-9a-f]+\>/i",			/* hex numbers */
			"/\([A-Za-z0-9_]+\)/",			/* parens */
			[ "/`/", "/`/" ],			/* backticks */
			[ "/<<[\"']?EOF[\"']?;$/m", "/^EOF$/m" ]	/* 'document here' */
		],
		'comments',	[ "/#.*$/m" ]
	],
	'detect'	=>	sub (d) {
		/* take the first line */
		local f = d.txt.lines[0];

		/* is it a 'she-bang' for usual shells? */
		return regex(f, '/^#!\s*/bin/(sh|bash|csh|dash|ksh)/') ||
		       regex(f, '/^#!\s*/usr/bin/make -f/');
	},
	'section'	=>	[ '/(^\w+\(\))/', '/^[A-Za-z0-9_\.-]+:/' ],
    'build_targets' => [
        'sh %s',
        'bash %s',
        'bash -n %s && echo "Syntax OK"'
    ]
};


mp.syntax.html = {
	'id'		=>	'html',
	'name'		=>	'HTML',
	'filenames'	=>	[ '/\.html$/i', '/\.htm$/i' ],
	'defs'		=>	[
		'word1',	[
				"/<[\/]?[ \t]*(" ~
				join([
				"a", "abbr", "acronym", "address",
				"area", "b", "base", "bdo", "big",
				"blockquote", "body", "br", "button",
				"caption", "center", "cite", "code", "col",
				"colgroup", "dd", "del", "dfn", "div",
				"dl", "dt", "em", "fieldset", "form",
				"h1", "h2", "h3", "h4", "h5", "h6",
				"head", "hr", "html", "i", "img",
				"input", "ins", "kbd", "label", "legend",
				"li", "link", "map", "meta", "noscript",
				"object", "ol", "optgroup", "option",
				"p", "param", "pre", "q", "samp",
				"script", "select", "small", "span",
				"strong", "style", "sub", "sup", "table",
				"tbody", "td", "textarea", "tfoot", "th",
				"thead", "title", "tr", "tt", "ul",
				"var" ], "|") ~
				")[^<>]*>/i"
		],
		'word2',	[
				mp.syn_token_list( [
				 "!DOCTYPE", "class", "type",
				"cellspacing", "cellpadding",
				"href", "align", "valign", "name", "lang",
				"value", "action", "width", "height",
				"content", "http-equiv", "src", "alt",
				"bgcolor", "text", "link", "vlink", "alink",
				"media", "id", "rel" ])
		],
		'quotes',	[
			"/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/"		/* single-quoted strings */
		],
		'comments',	[
				[ '/<!--/', '/-->/' ]
		]
	],
	'section'	=>	[ '/<\s*h[0-9]\s*>/' ]
};


mp.syntax.conf = {
	'id'		=>	'conf',
	'name'		=>	'Config file',
	'filenames'	=>	[ '/\.conf$/i', '/\.cfg$/i', '/^.*\/?\.[0-9a-z_-]+rc$/i' ],
	'defs'		=>	[
		'word1',	[ '/^\[.+\]$/m' ],
		'word2',	[ "/^[^:=\n]+[:=]/m" ],
		'quotes',	[
			"/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/",		/* single-quoted strings */
			"/\<-?[0-9]+\>/",			/* numbers */
			"/\<0x[0-9a-f]+\>/i"			/* hex numbers */
		],
		'comments',	[ "/#.*$/m" ]
	]
};


mp.syntax.php = {
	'id'		=>	'php',
	'name'		=>	'PHP',
	'filenames'	=>	[ '/\.php[345]?$/i', '/\.inc$/i' ],
	'defs'		=>	[
		'word1',	[
				mp.syn_token_list( [
				"and", "array", "as",
				"bool", "boolean", "break", "case", "class",
				"const", "continue", "declare", "default",
				"die", "do", "double", "echo", "else", "elseif",
				"empty", "enddeclare", "endfor", "endforeach",
				"endif", "endswitch", "endwhile", "eval",
				"exit", "extends", "__FILE__", "float", "for",
				"foreach", "function", "cfunction", "global",
				"if", "include", "include_once", "int",
				"integer", "isset", "__LINE__", "list", "new",
				"object", "old_function", "or", "print", "real",
				"require", "require_once", "return",
				"static", "string", "switch", "unset", "use",
				"var", "while", "xor", 'true', 'false' ]
				)
		],
		'word2',	[ '/\$\w+/', '/[-=<>:\?\+\*\!\%&\|]+/' ],
		'quotes',	[
			"/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/",		/* single-quoted strings */
			"/\<-?[0-9]+\>/",			/* numbers */
			"/\<0x[0-9a-f]+\>/i",			/* hex numbers */
			"/\<[0-9[:upper:]_]+\>/",		/* all-caps words */
			[ "/`/", "/`/" ]			/* backticks */
		],
		'comments',	[
			[ '|/\*|', '|\*/|' ],			/* C-like */
			'|//.*$|m'				/* C++ */
		]
	]
};

mp.syntax.python = {
	'id'		=>	'python',
	'name'		=>	'Python',
	'filenames'	=>	[ '/\.py$/i' ],
	'help'		=>	[
                        "python -c 'help(%s)'",
                        "python -c 'import %s as m; help(m)'"
                    ],
	'defs'		=>	[
		'word1',	[
			mp.syn_token_list( [
				"and", "assert", "break", "class", "continue",
				"def", "del", "elif", "else", "except", "exec",
				"finally", "for", "from", "if", "import", "in",
				"is", "lambda", "not", "or", "pass", "print",
				"raise", "return", "try", "while", "yield"
				]
			)
		],
		'word2',  [
            '/global/', '/\$\w+/', '/__[a-z]+__/',
            mp.syn_token_list([
                "True", "False", "None", "self",
                "dict", "list", "set", "bool",
                "int", "float", "str", "repr"
                ]
            )
        ],
        'word3', [
            '/]/',
            '/[-=<>:\.\?\+\*\!\%&\|{}\[\/]+/'
        ],
		'quotes',	[
			"/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/",		/* single-quoted strings */
			"/\<-?[0-9]+\>/",			/* numbers */
			"/\<0x[0-9a-f]+\>/i",			/* hex numbers */
			"/\<[0-9[:upper:]_]+\>/"		/* all-caps words */
		],
		'comments',		[ "/#.*$/m" ],
		'documentation',	[ [ '/"""/m', '/"""$/m' ] ]
	],
	'detect'	=>	sub (d) {
		/* take the first line */
		local f = d.txt.lines[0];

		/* is it a 'she-bang'? */
		return regex(f, '/^#!\s*/usr/bin/(env )?python/');
	},
	'section'	=>	[ '/^[ \t]*(def|class) /' ],
    'build_targets' => [
        'python %s',
        'python -m py_compile %s && echo "Syntax OK"'
    ]
};

mp.syntax.ruby = {
	'id'		=>	'ruby',
	'name'		=>	'Ruby',
	'filenames'	=>	[ '/\.rb$/i' ],
	'defs'		=>	[
		'word1',	[
			mp.syn_token_list( [
				"BEGIN", "END", "alias", "and", "begin",
				"break", "case", "class", "def", "defined",
				"do", "else", "elsif", "end", "ensure",
				"false", "for", "if", "in", "module", "next",
				"nil", "not", "or", "redo", "rescue", "retry",
				"return", "self", "super", "then", "true",
				"undef", "unless", "until", "when", "while",
				"yield", "require", "include" ]
			)
		],
		'word2',	[ '/[-=<>:\?\+\*\!\%&\|{}]+/', '/=(begin|end)/' ],
		'quotes',	[
			"/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/",		/* single-quoted strings */
			"/\<-?[0-9]+\>/",			/* numbers */
			"/\<0x[0-9a-f]+\>/i",			/* hex numbers */
			"/\<[0-9[:upper:]_]+\>/",		/* all-caps words */
			[ "/`/", "/`/" ]			/* backticks */
		],
		'comments',	[ "/#.*$/m" ]
	],
	'detect'	=>	sub (d) {
		/* take the first line */
		local f = d.txt.lines[0];

		/* is it a 'she-bang'? */
		return regex(f, '/^#!\s*/usr/bin/(env )?ruby/');
	}
};

mp.syntax.diff = {
    id:         'diff',
    name:       'diff',
    filenames:  [
        '/\.diff$/i',
        '/\.patch$/i'
    ],
    defs: [
        'word1',    [ '/^\+.+$/m' ],
        'word2',    [ '/^\-.+$/m' ],
        'quotes',   [ '/^@@.+@@.*$/m' ]
    ],
    section:        [ '/^@@/' ],
    detect:         sub (d) {
        local r, n;

        n = 0;
        while (n < 100) {
            if (regex(d.txt.lines[n++], '/^--- /'))
                break;
        }

        if (regex(d.txt.lines[n++], '/^\+\+\+ /') &&
            regex(d.txt.lines[n++], '/^@@ /'))
            r = 1;

        return r;
    }
};

mp.syntax.commit_msg = {
	'id'		=>	'commit_msg',
	'name'		=>	'VCS commit message',
	'filenames'	=>	[ '/sv[kn]-commit.*\./' ],
	'defs'		=>	[
		'word1',	[ '/^AM? .+$/m' ],
		'word2',	[ '/^D .+$/m' ],
		'quotes',	[ '/^M .+$/m' ],
		'comments',	[ '/^=== .+$/m', '/^--.+$/m' ]
	]
};

mp.syntax.po = {
	'id'		=>	'po',
	'name'		=>	'Gettext file',
	'filenames'	=>	[ '/\.po$/i' ],
	'defs'		=>	[
		'word1',	[ '/^msgid/m' ],
		'word2',	[ '/^msgstr/m' ],
		'comments',	[ "/#.*$/m" ],
		'quotes',	[
			"/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/"	/* double-quoted strings */
		]
	]
};

mp.syntax.xml = {
	'id'		=>	'xml',
	'name'		=>	'XML / XGML',
	'filenames'	=>	[ '/\.xml$/i', '/\.sgml$/i' ],
	'defs'		=>	[
		'word1',	[ '/<[^>]+>/' ],
		'word2',	[ '/<\?[^\?]+\?>/' ],
		'quotes',	[
			"/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/"		/* single-quoted strings */
		],
		'comments',	[
				[ '/<!--/', '/-->/' ]
		]
	],
	'detect'	=>	sub (d) {
		/* take the first line */
		local f = d.txt.lines[0];

		return regex(f, '/<\?xml/');
	}
};

mp.syntax.make_output = {
	'id'		=>	'make_output',
	'name'		=>	'Make output',
	'defs'		=>	[
		'word1',	[ "/^.*warning:.*$/m" ],
		'word2',	[ "/^.*error:.*$/m" ]
	]
};

mp.syntax.euphoria = {
	'id'		=>	'euphoria',
	'name'		=>	'euphoria',
	'filenames'	=>	[ '/\.e$/', '/\.eu$/', '/\.ew$/', '/\.ed$/',
				'/\.ex$/', '/\.exw$/', '/\.exu$/' ],
	'defs'		=>	[
		'word1',	[
			mp.syn_token_list( [
				"as", "and", "break", "by", "case", "constant", "continue", "do", "end",
				"else", "elsif", "elsifdef",
				"exit", "entry", "enum", "export", "for", "function", "global", "include",
				"if", "ifdef", "label",
				"not", "or", "procedure", "return", "retry", "switch", "then", "type",
				"to", "while", "with", "without", "xor"
			] )
		],
		'word2',	[
			mp.syn_token_list( [
				"atom", "integer", "sequence", "object"
			] )
		],
		'quotes',	[
			"/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/",    /* double-quoted strings */
			"/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/",        /* single-quoted strings */
			"/\<-?#[0-9A-F]+\>/",                /* hex numbers */
			"/\<-?[0-9]+\>/",                /* numbers */
			"/\<[0-9[:upper:]_]+\>/"            /* all-caps words */
		],
		'comments',		[ "/--.*$/m" ]
	],
	'section'	=>	[ '/^[ \t]*(global|export)*[ \t]*(function|procedure)/' ]
};


mp.syntax.mp_templates = {
	'id'		=> 'mp_templates',
	'name'		=> 'Minimum Profit template file',
	'filenames'	=> [ '/\.mp_templates$/' ],
	'defs'		=> [
		'documentation',	[ '/^%%.*$/m' ]
	],
	'section'	=> [ '/^%%/' ]
};


mp.syntax.hex_view = {
	'id'		=> 'hex_view',
	'name'		=> 'Hexadecimal view',
	'defs'		=> [
		'word1',		[ "/^\| [0-9A-F]+ \|/m" ],
		'word2',		[ "/\|/" ]
	]
};


mp.syntax.grutatxt = {
	'id'		=> 'grutatxt',
	'name'		=> 'Grutatxt',
	'defs'		=> [
		'word1',	[ "/[^\n]+\n[-=~]+\n/", "/ \* [^.\n:]+:/" ],
		'comments',	[ "/\<_[^ \t\n][^_\n]*[^ \t\n]_\>/" ],
		'word2',	[ "/\*[^ \t\n][^\*\n]*[^ \t\n]\*/" ],
		'quotes',	[ "/`[^ \t\n][^'\n]*[^ \t\n]'/" ]
	],
	'section'	=> [ "/^[-=~]+$/" ],
	'detect'	=>	sub (d) {
		local n = 0;

		/* search the first lines for a heading */
		while (n < 40) {
			local l = d.txt.lines[n++];

			if (l == NULL)
				break;

			if (regex(l, "/^[-=~]+$/"))
				return 1;
		}

		return 0;
	}
};


mp.syntax.js = {
    id:         'js',
    name:       'JavaScript',
    filenames:  [
        '/\.js$/i'
    ],
    defs:       [
        'word1', [
            mp.syn_token_list(
                [
                    "break", "case", "catch", "continue", "debugger",
                    "default", "delete", "do", "else", "finally",
                    "for", "function", "if", "in", "instanceof",
                    "return", "switch", "throw", "try", "while", "with"
                ]
            )
        ],
        'word2', [
            mp.syn_token_list(
                [
                    "new", "this", "typeof", "var", "void"
                ]
            )
        ],
        'word3', [
            '/[-=<>:\?\+\*\/\!\%&\|~\^\.\{\}\(\)\[]+/',
            '/]/'
        ],
        'quotes', [
            /* from http://ad.hominem.org/log/2005/05/quoted_strings.php */
            "/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/", /* double-quoted strings */
            "/'([^'\\\\\n]*(\\\\.[^'\\\\\n]*)*)'/",     /* single-quoted strings */
            "/\<-?[0-9]+\>/",                           /* numbers */
            "/\<[0-9]+e[0-9]+\>/",                      /* numbers in exp format */
            "/\<0[0-7]+\>/",                            /* octal numbers */
            "/\<0x[0-9a-f]+\>/i",                       /* hex numbers */
            "/\<(true|false|null)\>/"                   /* symbolic constant names */
        ],
        'comments', [
            [ '|/\*|', '|\*/|' ],                       /* C-like */
            '|//.*$|m'                                  /* C++ */
        ],
        'documentation', [
            [ "|/\*\*\n|", '|\*/|' ],                   /* mp_doccer */
            '/^/\*\*.*\*\*/$/m'                         /* section mark */
        ]
    ],
    section:    [ '/(^\/\*\*.*\*\*\/$|^#pragma mark|^[A-Za-z_])/' ]
};


mp.syntax.json = {
    id:         'json',
    name:       'JSON',
    filenames:  [
        '/\.json$/i'
    ],
    defs:       [
        'word1', [
            "/\<-?[0-9]+\>/"
        ],
        'quotes', [
            "/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"/"
        ],
        'word2', [
            "/\"([^\"\\\\\n]*(\\\\.[^\"\\\\\n]*)*)\"\s*:/"
        ],
        'word3', [
            "/\<(true|false|null)\>/"
        ]
    ]
};


/** code **/

sub mp_doc.detect_syntax(doc)
/* tries to detect the syntax of a document */
{
    doc.syntax = NULL;

    /* loops the syntax highlight definitions */
    foreach (n, keys(mp.syntax)) {
        local s = mp.syntax[n];

        /* test the extensions */
        foreach (ext, s.filenames) {
            if (regex(doc.name, ext)) {
                doc.syntax = s;
                return doc;
            }
        }
    }

    /* not by extension? try the 'detect' subroutine */
    foreach (n, keys(mp.syntax)) {
        local s = mp.syntax[n];

        if (is_exec(s.detect) && s.detect(doc)) {
            doc.syntax = s;
            return doc;
        }
    }

    return doc;
}


sub mp_doc.help(doc, word)
{
    local h;

    foreach (c, doc.syntax.help) {
        local f;

        /* format the command */
        c = sprintf(c, word);

        /* pipe from it */
        if ((f = popen(c, "r")) != NULL) {
            local l;
            h = [];

            while ((l = read(f)) != NULL) {
                l = sregex(l, "/\x{0008}./g");
                push(h, mp.chomp(l));
            }

            /* fails? */
            if (pclose(f) != 0)
                h = NULL;
        }

        /* is there already help? don't look for more */
        if (h != NULL)
            break;
    }

    if (h == NULL && doc.syntax.help_func)
        h = doc.syntax.help_func(word);

    return h;
}


/**
 * mp_doc.section_list - Returns the list of sections of a document.
 * @doc: the document
 *
 * Applies the `section' array of regular expressions of the
 * document's syntax definition and returns it as an array of
 * line and line number pairs.
 *
 * If the document has no syntax highlight definition, or it has
 * one without a `section' definition, NULL is returned. Otherwise,
 * an array of line, line number pairs is returned (it can be
 * an empty list if the document has no recognizable sections).
 */
sub mp_doc.section_list(doc)
{
    local r;

    if (doc.syntax.section) {
        local n, l;

        r = [];

        while ((l = doc.txt.lines[n]) != NULL) {
            foreach (ex, doc.syntax.section) {
                if (regex(l, ex)) {
                    push(r, [ l, n ]);
                    break;
                }
            }

            n++;
        }
    }

    return r;
}


/**
 * mp.c_gather_help - Gathers help in C-style files
 * @word: the help term
 *
 * Searches in all applicable files for code snippets that may
 * conform help for @word (mp_doccer style help, struct or
 * function definitions, etc).
 *
 * Returns an array of text with the gathered information.
 */
sub mp.c_gather_help(word)
{
    local h = [];

    foreach (fn, glob()) {
        local f;
        local ok = 0;

        /* test if this file is relevant to C */
		foreach (ext, mp.syntax.c.filenames) {
			if (regex(fn, ext)) {
                ok = 1;
                break;
			}
        }

        if (ok && (f = open(fn, "r")) != NULL) {
            local l;
            local n = 0;

            while ((l = read(f)) != NULL) {
                n++;
                local where = fn ~ ':' ~ n ~ ':';

                l = mp.chomp(l);

                if (regex(l, "/^[ \t]*#define[ \t]+" ~ word ~ "/")) {
                    /* it's a CPP define */
                    push(h, where, l);
                }
                else
                if (regex(l, "/^[A-Za-z_]+.*[ \t]+" ~ word ~ "[ \t]*\(.*/")) {
                    /* it's a function definition or prototype */
                    push(h, where, l);
                }
                else
                if (regex(l, "/^(typedef[ \]*|static[ \]*)?struct[ \t]+.*" ~ word ~ ".*\{/")) {
                    /* it's a structure definition */
                    push(h, where, l);

                    /* add up to the end of the struct */
                    while ((l = read(f)) != NULL) {
                        n++;
                        push(h, l);

                        if (regex(l, "/^\};/"))
                            break;
                    }
                }
                else
                if (regex(l, "/^\/\*\*$/")) {
                    /* mp_doccer help: is it for this word? */
                    l = read(f);
                    n++;

                    if (regex(l, "/ \* " ~ word ~ " - /")) {
                        /* it is; dump up to a ; */
                        push(h, where, '/' ~ '**', l);

                        while ((l = read(f)) != NULL) {
                            n++;
                            if (regex(l, "/^\{/"))
                                break;

                            push(h, l);
                        }
                    }
                }
            }

            close(f);
        }
    }

    if (size(h)) {
        ins(h, sprintf(L("Gathered information on %s:"), word), 0);
        ins(h, '', 1);
    }
    else
        h = NULL;

    return h;
}
