/*

    Minimum Profit 5.x
    A Programmer's Text Editor

    Syntax highlight definitions.

    Copyright (C) 1991-2007 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://www.triptico.com

*/

/* editor actions */

mp.actions['help']	=	sub (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 = mp.get_word(d, r)) != NULL)
		h = mp.help(d, w);

	if (h == NULL)
		mp.alert(sprintf(L("No help for '%s'."), w));
	else
		mp.new(sprintf(L("<help on '%s'>"), w), h);
};

/* default key bindings */

mp.keycodes['f1']	=	"help";

/* action descriptions */

mp.actdesc['help'] = LL("Help on word over cursor");

/* data */

mp.syntax = {};

/* code */

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

mp.syntax.c = {
	'id'		=>	'c',
	'name'		=>	'C / C++',
	'filenames'	=>	[ '/\.c$/', '/\.h$/', '/\.l$/', '/\.y$/', '/\.d$/',
				'/\.cpp$/', '/\.hpp$/', '/\.c++$/', '/\.xpm$/' ],
	'help'		=>	[ "man 2 %s", "man 3 %s" ],
	'defs'		=>	[
		'word1',	[
				mp.syn_token_list( [
				"for", "while", "if", "switch", "case", "do",
				"else", "break", "continue", "return",
				"default", "goto", "main", "fopen", "fclose",
				"fgets", "fgetc", "fputs", "fputc", "fprintf",
				"putc", "printf", "sprintf", "strcpy", "strcat",
				"strcmp", "strncmp", "strtok", "stricmp", "strchr",
				"strrchr", "strlen", "memcmp", "memcpy", "malloc",
				"free", "strncpy", "strncat", "snprintf", "strstr",
				"memset", "memcpy", "va_start", "va_end", "vsprintf",
				"vsnprintf", "atoi", "qsort", "bsearch", "getenv",
				"fscanf", "popen", "pclose", "realloc", "fread",
				"fwrite", "fseek", '\{', '\}', "putchar", "fflush",
				"wcscmp", "swprintf", "wmemcpy", "swscanf", "sscanf",
				"wcslen", "wmemset", "wcscpy", "wcsncpy", "wcscoll",
				"mbstowcs", "wcstombs", "wprintf", "wcschr",
				"wcsrchr", "wcsstr", "strdup", "wctomb", "mbtowc",
				"mbrtowc", "wcrtomb", "open", "close", "read",
				"write", "pipe", "fork", "dup", "dup2", "wait",
				"execl", "execlp", "execle", "execv", "execvp"
				])
		],
		'word2',	[
				mp.syn_token_list( [
				"char", "int", "long", "struct", "union", "const",
				"void", "unsigned", "signed", "auto", "volatile",
				"enum", "typedef", "float", "double", "extern",
				"register", "short", "sizeof", "static", "far",
				"near", "defined", "va_list", "size_t", "wchar_t",
				"iconv_t" ]),
				'/[-=<>:\?\+\*\/\!\%&\|]+/'
		],
		'quotes',	[
			/* from http://ad.hominem.org/log/2005/05/quoted_strings.php */
			"/\"([^\"\\\n]*(\\.[^\"\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\n]*(\\.[^'\\\n]*)*)'/",	/* single-quoted strings */
			"/\b-?[0-9]+\b/",			/* numbers */
			"/\b0x[0-9a-f]+\b/i",			/* hex numbers */
			"/\b[0-9[:upper:]_]+\b/"		/* all-caps words */
		],
		'comments',	[
			[ '|/\*|', '|\*/|' ],			/* C-like */
			'|//.*$|m',				/* C++ */
			'/^\s*#[a-z]+/m'			/* CPP directives */
		]
	]
};

mp.syntax.rc = {
	'id'		=>	'rc',
	'name'		=>	'Resource file',
	'filenames'	=>	[ '/\.rc$/' ],
	'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 */
			"/\b-?[0-9]+\b/",			/* numbers */
			"/\b0x[0-9a-f]+\b/i",			/* hex numbers */
			"/\b[0-9[:upper:]_]+\b/"		/* 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$/' ],
	'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 */
			"/\b-?[0-9]+\b/",			/* numbers */
			"/\b0x[0-9a-f]+\b/i",			/* hex numbers */
			"/\b[0-9[:upper:]_]+\b/",		/* 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 */
			"/__END__\n.*$/",			/* __END__ */
			[ "/^=(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('/^#!/usr/bin/(env )?perl/', f);
	}
};


mp.syntax.mpsl = {
	'id'		=>	'mpsl',
	'name'		=>	'MPSL',
	'filenames'	=>	[ '/\.mpsl$/' ],
	'defs'		=>	[
		'word1',	[
				mp.syn_token_list( [
					'if', 'else', 'while', 'foreach',
					'sub', 'break', 'return', 'eq', 'ne' ]),
				mp.syn_token_list( keys(MPSL.CORE) ),
				'/[\{\}]/'
		],
		'word2',	[
				"/(NULL|local)/",
				'/[-=<>:\?\+\*\/\!\%&\|]+/'
		],
		'quotes',	[
			/* from http://ad.hominem.org/log/2005/05/quoted_strings.php */
			"/\"([^\"\\\n]*(\\.[^\"\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\n]*(\\.[^'\\\n]*)*)'/",	/* single-quoted strings */
			"/\b-?[0-9]+\b/",			/* numbers */
			"/\b0x[0-9a-f]+\b/i",			/* hex numbers */
			"/\b[0-9[:upper:]_]+\b/"		/* all-caps words */
		],
		'comments',	[
			[ '|/\*|', '|\*/|' ],			/* C-like */
			'|//.*$|m',				/* C++ */
			'/^\s*#[a-z]+/m'			/* CPP directives */
		]
	]
};

mp.syntax.sh = {
	'id'		=>	'sh',
	'name'		=>	'Shell script',
	'filenames'	=>	[ '/\.sh$/', '/makefile/i' ],
	'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',	[
				'/\b(local|let|set)\b/',
				'/[-=<>:\?\+\*\!\%&\|]+/',
				'/\$\w+/',
				'/\$\{\w+\}/',
				"/\b[0-9[:upper:]_]+\b/"	/* all-caps words */
		],
		'quotes',	[
			"/\"([^\"\\\n]*(\\.[^\"\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\n]*(\\.[^'\\\n]*)*)'/",	/* single-quoted strings */
			"/\b-?[0-9]+\b/",			/* numbers */
			"/\b0x[0-9a-f]+\b/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('/^#!/bin/(sh|bash|csh|dash|ksh)/', f) ||
		       regex('/^#!/usr/bin/make -f/', f);
	}

};


mp.syntax.html = {
	'id'		=>	'html',
	'name'		=>	'HTML',
	'filenames'	=>	[ '/\.html$/', '/\.htm$/' ],
	'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" ])
		],
		'quotes',	[
			"/\"([^\"\\\n]*(\\.[^\"\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\n]*(\\.[^'\\\n]*)*)'/"	/* single-quoted strings */
		],
		'comments',	[
				[ '/<!--/', '/-->/' ]
		]
	]
};


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


mp.syntax.php = {
	'id'		=>	'php',
	'name'		=>	'PHP',
	'filenames'	=>	[ '/\.php[345]?$/', '/\.inc$/' ],
	'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 */
			"/\b-?[0-9]+\b/",			/* numbers */
			"/\b0x[0-9a-f]+\b/i",			/* hex numbers */
			"/\b[0-9[:upper:]_]+\b/",		/* all-caps words */
			[ "/`/", "/`/" ]			/* backticks */
		],
		'comments',	[
			[ '|/\*|', '|\*/|' ],			/* C-like */
			'|//.*$|m'				/* C++ */
		]
	]
};

mp.syntax.python = {
	'id'		=>	'python',
	'name'		=>	'Python',
	'filenames'	=>	[ '/\.py$/' ],
	'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+/', '/[-=<>:\?\+\*\!\%&\|{}]+/' ],
		'quotes',	[
			"/\"([^\"\\\n]*(\\.[^\"\\\n]*)*)\"/",	/* double-quoted strings */
			"/'([^'\\\n]*(\\.[^'\\\n]*)*)'/",	/* single-quoted strings */
			"/\b-?[0-9]+\b/",			/* numbers */
			"/\b0x[0-9a-f]+\b/i",			/* hex numbers */
			"/\b[0-9[:upper:]_]+\b/",		/* all-caps words */
			[ "/`/", "/`/" ]			/* backticks */
		],
		'comments',	[ "/#.*$/m", [ '/""".+[^"]$/m', '/"""$/m' ] ]
	],
	'detect'	=>	sub (d) {
		/* take the first line */
		local f = d.txt.lines[0];

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

mp.syntax.ruby = {
	'id'		=>	'ruby',
	'name'		=>	'Ruby',
	'filenames'	=>	[ '/\.rb$/' ],
	'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 */
			"/\b-?[0-9]+\b/",			/* numbers */
			"/\b0x[0-9a-f]+\b/i",			/* hex numbers */
			"/\b[0-9[:upper:]_]+\b/",		/* 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('/^#!/usr/bin/(env )?ruby/', f);
	}
};

mp.syntax.diff = {
	'id'		=>	'diff',
	'name'		=>	'diff',
	'filenames'	=>	[ '/\.diff$/', '/\.patch$/' ],
	'defs'		=>	[
		'word1',	[ '/^\+.+$/m' ],
		'word2',	[ '/^\-.+$/m' ],
		'quotes',	[ '/^@@.+@@$/m' ]
	]
};

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$/' ],
	'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$/', '/\.sgml$/' ],
	'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('/<\?xml/', f);
	}
};


sub mp.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(ext, doc.name)) {
				doc.syntax = s;
				return;
			}
		}
	}

	/* 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;
		}
	}
}


sub mp.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)
				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;
	}

	return h;
}
