No changes

This commit is contained in:
Frank Harris 2025-09-11 13:29:15 -04:00
parent 8680a02b13
commit b6b398f5bf
17374 changed files with 2475441 additions and 0 deletions

View file

@ -0,0 +1 @@
Options -Indexes

View file

@ -0,0 +1,502 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View file

@ -0,0 +1 @@
allow from all

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,281 @@
/**
* This simply adds some extras to Luminous elements via a jQUery
* plugin. The extras are currently a toggleable line-highlighting
* on click
*/
(function($) {
"use strict";
var LINE_SELECTOR = 'td .code > span ';
if (typeof $ === 'undefined') { return; }
/****************************************************************
* UTILITY FUNCTIONS *
****************************************************************/
// determines if the given element is a line element of luminous
function isLine($line) {
return $line.is(LINE_SELECTOR) && $line.parents('.luminous').length > 0;
}
function isLineNumber($element) {
return $element.is('.luminous .line-numbers span');
}
function highlightLine($line) {
$line.toggleClass('highlight');
}
function highlightLineByIndex($luminous, index) {
var $line = $luminous.find(LINE_SELECTOR).eq(index);
highlightLine($line);
}
function highlightLineByNumber($luminous, number) {
// the line's index must take into account the initial line number
var offset = parseInt($luminous.find('.code').data('startline'), 10);
if (isNaN(offset)) offset = 0;
highlightLineByIndex($luminous, number - offset);
}
function toggleHighlightAndPlain($luminous, forceState) {
var data = $luminous.data('luminous'),
state = data.code.active,
$elem = $luminous.find('.code'),
toSetCode, toSetState;
if (forceState === 'plain') state = 'highlighted';
else if (forceState === 'highlighted') state = 'plain';
toSetCode = (state === 'plain')? data.code.highlighted : data.code.plain;
toSetState = (state === 'plain')? 'highlighted' : 'plain';
$elem.html(toSetCode);
}
function toggleLineNumbers($luminous, forceState) {
var data = $luminous.data('luminous'),
show = (typeof forceState !== 'undefined')? forceState :
!data.lineNumbers.visible;
data.lineNumbers.visible = show;
var $numberContainer = $luminous.find('.line-numbers'),
$control = $luminous.find('.line-number-control');
if (!show) {
$numberContainer.addClass('collapsed');
$control.addClass('show-line-numbers');
$luminous.addClass('collapsed-line-numbers');
} else {
$numberContainer.removeClass('collapsed');
$control.removeClass('show-line-numbers');
}
$luminous.data('luminous', data);
}
// binds the event handlers to a luminous element
function bindLuminousExtras($element) {
var highlightLinesData, highlightLines, data = {},
hasLineNumbers = $element.find('td .line-numbers').length > 0,
schedule = [];
if (!$element.is('.luminous')) { return false; }
else if ($element.is('.bound')) { return true; }
$element.addClass('bound');
// highlight lines on click
$element.find('td .code').click(function(ev) {
var $t = $(ev.target);
var $lines = $t.parents().add($t).
filter(function() { return isLine($(this)); }),
$line
;
if ($lines.length > 0) {
$line = $lines.eq(0);
highlightLine($line);
}
});
// highlight lines on clicking the line number
$element.find('td .line-numbers').click(function(ev) {
var $t = $(ev.target),
index;
if ($t.is('span')) {
index = $t.prevAll().length;
highlightLineByIndex($element, index);
}
});
data.lineNumbers = {visible: false};
if (hasLineNumbers) {
/**
* Line numbering is semi complicated because we can make it better
* with javascript!
* TODO: probably refactor this into a sub-function
*/
// the control is a show/hide line numbers, we can fade it
// in/out when the user hovers over the line numbers.
// We can also fix the line numbers so they move left
// as the widget is hoz-scrolled.
var $control, controlHeight, controlWidth, gutterWidth,
controlIsVisible = false,
$lineNumbers = $element.find('pre.line-numbers'),
defaultLineNumberWidth = $lineNumbers.outerWidth(),
mouseY = 0,
controlCalculateLeftCss = function() {
var visible = $element.data('luminous').lineNumbers.visible,
base = visible? gutterWidth - controlWidth : 0,
total = 0;
total = $element.scrollLeft() + base;
return total + 'px';
};
data.lineNumbers.visible = true;
data.lineNumbers.setControlPosition = function() {
$control.css('top', Math.max(0, mouseY - (controlHeight/2)) + 'px');
}
$control = $('<a class="line-number-control"></a>');
$control.click(function() {
$element.luminous('showLineNumbers');
$control.css('left', controlCalculateLeftCss());
if (!$element.data('luminous').lineNumbers.visible) {
$element.find('pre.code').css('padding-left', '');
} else {
$element.find('pre.code').css('padding-left', defaultLineNumberWidth + 'px');
}
});
$control.appendTo($element);
$control.show();
controlWidth = $control.outerWidth();
controlHeight = $control.outerHeight();
gutterWidth = $element.find('.line-numbers').outerWidth();
$control.css('left', gutterWidth - controlWidth + 'px');
$control.hide();
$element.mousemove(function(ev) {
var scrollLeft = $element.scrollLeft();
mouseY = ev.pageY - $(this).offset().top;
if (ev.pageX < gutterWidth) {
if (!controlIsVisible) {
data.lineNumbers.setControlPosition();
$control.stop(true, true).fadeIn('fast');
controlIsVisible = true;
}
} else {
if (controlIsVisible) {
$control.stop(true, true).fadeOut('fast');
controlIsVisible = false;
}
}
});
data.lineNumbers.setControlPosition();
$element.find('pre.code').css('padding-left', $lineNumbers.outerWidth() + 'px');
$lineNumbers.css({
position: 'absolute',
top: 0,
left: 0
});
$element.scroll(function() {
data.lineNumbers.setControlPosition();
$control.css('left', controlCalculateLeftCss());
$lineNumbers.css('left', $element.scrollLeft() + 'px');
});
schedule.push(function() { $element.luminous('showLineNumbers', true); });
$element.find('.line-numbers').parent().css({width: 0, maxWidth: 0});
}
// highlight all the initial lines
highlightLinesData = $element.find('.code').data('highlightlines') || "";
highlightLines = highlightLinesData.split(",");
$.each(highlightLines, function(i, element) {
var lineNo = parseInt(element, 10);
if (!isNaN(lineNo)) {
highlightLineByNumber($element, lineNo);
}
});
data.code = {};
data.code.highlighted = $element.find('.code').html();
data.code.plain = '';
$element.find(LINE_SELECTOR).each(function(i, e) {
var line = $(e).text();
line = line
.replace(/&/g, '&amp')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
data.code.plain += '<span>' + line + '</span>';
});
data.code.active = 'highlighted';
$element.data('luminous', data);
$.each(schedule, function(i, f) {
f();
});
}
/****************************************************************
* JQUERY PLUGIN *
***************************************************************/
$.fn.luminous = function(optionsOrCommand /* variadic */) {
var args = Array.prototype.slice.call(arguments);
return $(this).each(function() {
var $luminous = $(this);
// no instructions - bind everything
if (!optionsOrCommand) {
bindLuminousExtras($luminous);
return;
}
// $('.luminous').luminous('highlightLine', [2, 3]);
if (optionsOrCommand === 'highlightLine') {
var lineNumbers = args[1];
if (!$.isArray(lineNumbers))
lineNumbers = [lineNumbers];
$.each(lineNumbers, function(index, el) {
highlightLineByNumber($luminous, el);
});
return;
}
else if (optionsOrCommand === 'show') {
// args[1] should be 'highlighted' or 'plain'
toggleHighlightAndPlain($luminous, args[1]);
}
else if (optionsOrCommand === 'showLineNumbers') {
toggleLineNumbers($luminous, args[1]);
}
});
};
$(document).ready(function() {
$('.luminous').luminous();
});
}(jQuery));

View file

@ -0,0 +1,128 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Luminous Syntax Highlighter</title>
<link rel='stylesheet' type='text/css' href='style/luminous.css'>
<link rel='stylesheet' type='text/css' href='style/geonyx.css'>
<style>
h1{font-size: x-large;}
h2{font-size: large; }
h3{font-size: medium;}
body{ margin:1em; font-family: sans-serif; font-size:12pt;}
.luminous { border: 1px solid #bbb; }
span.inline-code {
background-color: #E7E7E7;
padding: 1px 0.25em;
font-family: monospace;
font-weight: bold;
color: #323232;
border: 1px solid #888;
}
</style>
</head>
<body>
<h1>Luminous - a Syntax Highlighter for PHP - v0.7.0</h1>
<p><a href="http://travis-ci.org/markwatkinson/luminous"><img src="https://secure.travis-ci.org/markwatkinson/luminous.png" alt="Build Status" title="" /></a></p>
<p>Luminous is an accurate and style-able syntax highlighter for PHP which
supports a bunch of common languages and output to HTML and LaTeX.</p>
<p>If you simply want to use Luminous as a library, <strong>please don't clone this
repository</strong>. Or if you do, make sure you delete luminous/tests afterwards.
Do not expose luminous/tests on a public machine. It is recommended to get a
packaged version from the links below.</p>
<h2>Links:</h2>
<ul>
<li><a href="http://luminous.asgaard.co.uk/">Luminous PHP syntax highlighter official site</a> - news, latest stable versions, etc</li>
<li><a href="http://luminous.asgaard.co.uk/index.php/demo">Online demo</a></li>
<li><a href="http://luminous.asgaard.co.uk/index.php/docs/show/index">Documentation and help</a>,
read this if you get stuck!</li>
<li><a href="http://luminous.asgaard.co.uk/assets/luminous/supported.php">Supported language list</a></li>
<li><a href="https://github.com/markwatkinson/luminous">Luminous on GitHub</a> - please
report problems to the issue tracker here</li>
</ul>
<h1>Installation</h1>
<p>Extract your tarball, zip, whatever, into some directory where it's going to be
used (i.e. probably your web-server). We'll assume it's called `luminous/'</p>
<h1>Quick Usage </h1>
<p>First, if you're going to use caching, which you probably are, create a
directory called luminous/cache and give it writable permissions (chmod 777 on
most servers -- yours may accept a less permissive value). Then include
luminous/luminous.php and away you go!</p>
<p><div
class="luminous"
data-language="PHP"
style=""
><table><tbody><tr>
<td><pre class="line-numbers"><span>1</span><span>2</span><span>3</span><span>4</span></pre></td>
<td class="code-container"><pre class="code numbered"
data-startline="1"
data-highlightlines=""
><span><span class='delimiter'>&lt;?php</span>
</span><span><span class='function'>require_once</span> <span class='string'>'luminous/luminous.php'</span><span class='operator'>;</span>
</span><span><span class='function'>echo</span> <span class='obj'>luminous</span><span class='operator'>::</span><span class='oo'>head_html</span>()<span class='operator'>;</span> <span class='comment'>// outputs CSS includes, intended to go in &lt;head&gt;</span>
</span><span><span class='function'>echo</span> <span class='obj'>luminous</span><span class='operator'>::</span><span class='oo'>highlight</span>(<span class='string'>'c'</span><span class='operator'>,</span> <span class='string'>'printf("hello world<span class='esc'>\n</span>");'</span>)<span class='operator'>;</span>
</span></pre></td></tr></tbody></table></div></p>
<p>Useful examples can be found in luminous/examples/. If you have problems,
check that luminous/examples/example.php works.</p>
<h1>Command Line Usage</h1>
<p>If you're crazy and want to use Luminous/PHP on the command line, guess what,
you can!</p>
<p><div
class="luminous"
data-language="Bash"
style=""
><table><tbody><tr><td><pre class="line-numbers"><span>1</span><span>2</span></pre></td><td class="code-container"><pre class="code numbered"
data-startline="1"
data-highlightlines=""
><span>$ <span class='function'>cd</span> luminous/
</span><span>$ php luminous.php --help
</span></pre></td></tr></tbody></table></div></p>
<h1>Polite Warning</h1>
<p>Luminous is fairly slow. But it caches! So it's not slow. Or is it?</p>
<p>It depends on your use-case, is the simple answer. Most people should make sure
the cache works (create luminous/cache with appropriate permissions), and after
that, Luminous will almost certainly have negligable impact on their
performance.</p>
<p>Optimizations are welcome, but not at the expense of maintainability.</p>
<h2>Caching</h2>
<p>The cache can be stored either directly on the file system or in a MySQL table
(support for other DBMSs will come later, patches welcome). In either case,
check out the <a href="http://luminous.asgaard.co.uk/index.php/docs/show/cache">cache documentation</a>.</p>
<h1>Licensing</h1>
<p>Luminous is distributed under the LGPL but includes a bunch of stuff which is
separate.</p>
<ul>
<li>Everything under src/ and languages/ is part of Luminous.</li>
<li>Everything under tests/regression/<em>/</em> is real source code taken from various
projects, which is used only as test data. It is all GPL-compatible, but
is distributed under its own license. This directory is only present in
the git repository and is not part of any stable distribution archives.</li>
<li>We also include jQuery which is provided under its own license.</li>
</ul>
</body>
</html>

View file

@ -0,0 +1 @@
Deny from All

View file

@ -0,0 +1,64 @@
<?php
/*
* TODO: user defined types and stuff
*
*/
class LuminousAdaScanner extends LuminousSimpleScanner {
public function init() {
// http://en.wikibooks.org/wiki/Ada_Programming/Keywords
// http://en.wikibooks.org/wiki/Ada_Programming/All_Keywords
$kws = array('abort', 'abstract', 'accept', 'access', 'aliased',
'all', 'array', 'at',
'begin', 'body',
'case', 'constant',
'declare','delay', 'delta', 'digits', 'do',
'else', 'elsif', 'end', 'entry', 'exception', 'exit',
'for', 'function',
'generic', 'goto',
'if', 'interface', 'is',
'limited', 'loop',
'new',
'of', 'others', 'out', 'overriding',
'package', 'pragma', 'private', 'procedure', 'protected',
'raise', 'range', 'record', 'renames', 'requeue', 'return', 'reverse',
'select', 'separate', 'subtype', 'synchronized',
'tagged', 'task', 'terminate', 'then', 'type',
'until', 'use',
'when', 'while', 'with',
);
$ops = array('abs', 'and', 'in', 'mod', 'not', 'or', 'rem', 'xor');
$vals = array('false', 'null', 'true');
// http://en.wikibooks.org/wiki/Ada_Programming/Type_System#Predefined_types
$types = array('Float', 'Duration', 'Character', 'String', 'Boolean',
'Address', 'Storage_Offset', 'Storage_Count', 'Storage_Element',
'Storage_Array',
'Wide_character', 'Wide_Wide_Character',
'Wide_String', 'Wide_Wide_String',
'Integer',
'Long', 'Short', 'Byte');
$ident = '(?i:[a-z](?:_?[a-z]++|\d++)*+)';
// http://en.wikibooks.org/wiki/Ada_Programming/Lexical_elements#Identifiers
$this->add_pattern('OO', "/(?<=[a-z0-9_]')$ident/");
$this->add_pattern('IDENT', "/$ident/");
// http://en.wikibooks.org/wiki/Ada_Programming/Lexical_elements#Numbers
// no bnf :( might be wrong
$this->add_pattern('NUMERIC', '/\d+#[a-f0-9]*#/i');
$this->add_pattern('NUMERIC', "/[0-9]++[0-9_]*+(\.[0-9_]++)?([eE][\-+]?[0-9_]++)?/");
$this->add_pattern('COMMENT', '/--.*/');
$this->add_pattern('OPERATOR', '@=|/=|>=?|<=?|\+|-|\*\*?|/|&|:=@');
// http://rosettacode.org/wiki/Special_characters#Ada
$this->add_pattern('CHARACTER', "/'.'/");
$this->add_pattern('STRING', '/"(?:[^"]++|"")*"/');
$this->add_identifier_mapping('KEYWORD', $kws);
$this->add_identifier_mapping('OPERATOR', $ops);
$this->add_identifier_mapping('VALUE', $vals);
$this->add_identifier_mapping('TYPE', $types);
}
}

View file

@ -0,0 +1,69 @@
<?php
// as far as I know, actionscript and javascript are both derivatives of
// ECMA script, and therefore we can subclass JavaScript's scanner and just
// and override the identifier names.
// but we also override init so as to prevent any embedding
class LuminousActionScriptScanner extends LuminousECMAScriptScanner {
function init() {
$this->embedded_server = false;
$this->embedded_script = false;
parent::init();
// add preprocessor support
$this->add_pattern('PREPROCESSOR', '/\^\s*#.*/m');
// clear the identifier map for JS and insert our own.
// $this->ident_map = array();
$this->add_identifier_mapping('', array());
$this->add_identifier_mapping('FUNCTION', array('add', 'chr',
'clearInterval', 'escape', 'eval',
'evaluate', 'fscommand', 'getProperty', 'getTimer', 'getVersion',
'globalStyleFormat', 'gotoAndPlay', 'gotoAndStop', 'ifFrameLoaded',
'instanceOf', 'isFinite', 'isNaN', 'loadMovie', 'loadMovieNum',
'loadVariables', 'mbchr', 'mblength', 'mbord', 'mbsubstring', 'nextFrame',
'nextScene', 'onClipEvent',
'ord', 'parseFloat', 'parseInt', 'play', 'prevFrame', 'prevScene', 'print',
'printAsBitMap', 'printNum', 'printNum', 'random', 'scroll', 'setInterval',
'setProperty', 'stop', 'stopDrag', 'substring', 'super', 'targetPath',
'tellTarget', 'toString', 'toggleHighQuality', 'trace', 'unescape'));
$this->add_identifier_mapping('TYPE', array('Accessibility',
'Array', 'Arguments', 'Boolean',
'Button', 'ByteArray', 'Camera', 'Color', 'Date', 'Event', 'FScrollPane',
'FStyleFormat',
'Function', 'int', 'Key', 'LoadVars', 'LocalConnection', 'Math',
'Microphone', 'Mouse', 'Movieclip', 'Number', 'Object', 'Selection',
'Sound', 'Sprite', 'String', 'System', 'TextField', 'TextFormat',
'Timer', 'TimerEvent', 'uint', 'var', 'void', 'XML'));
$this->add_identifier_mapping('KEYWORD', array('as', 'break',
'case', 'catch', 'class', 'const', 'continue', 'default', 'delete',
'do', 'else', 'extends', 'false', 'finally', 'for', 'function',
'if', 'implements', 'import', 'in', 'instanceof', 'interface', 'internal',
'is', 'native', 'new', 'null', 'package', 'private', 'protected', 'public',
'return', 'super', 'switch', 'static', 'this', 'throw', 'to', 'true', 'try',
'typeof', 'use', 'void', 'while', 'with'));
}
public static function guess_language($src, $info) {
// actionscript looks a lot like a cross between Java and, umm, well,
// Java.
// It has a semi-unique way of declaring types for arguments and
// returns and so forth as argname:type, or function name(args):ret-type
$p = 0.0;
if (preg_match(
'/\\bfunction\s+\w+\s*\\([^\\)]+\\):(String|int|Number|void)/',
$src)) $p += 0.15;
if (preg_match('/\\bvar\s+\w+:(String|int|Number)/', $src)) $p += 0.15;
return $p;
}
}

View file

@ -0,0 +1,273 @@
<?php
/*
* XXX: I don't really know bash all that well. I don't know how the
* interpolation rules work exactly, i.e. if
* x=" $( # )"
* is a left unterminated by the comment or if the comment terminates at the )
* Kate things the latter and I'll go with it.
*
*
* 2011-10-10: Changed comment regex to require a preceding whitespace char
* (or start of string). This seems in line with Kate, and it prevents
* incorrectly hitting some things as comments which are actually
* [I have no idea]. e.g.: for (( i=0; i<${#1}; i=i+2 ));
*
* Also changed it to not apply any highlighting between (( ... ))
* blocks, which fixes a bug regarding shifts being detected as
* heredocs. This is buggy - it should detect SOME types inside
* these blocks, and it should be aware of nested brackets. FIXME.
*/
class LuminousBashScanner extends LuminousScanner {
public $interpolated = false;
public static function string_filter($token) {
$token = LuminousUtils::escape_token($token);
$token[1] = preg_replace("/\\$(?:\w+|\\{[^}\n]+\\})/",
'<VARIABLE>$0</VARIABLE>', $token[1]);
return $token;
}
public function init() {
$this->add_identifier_mapping('KEYWORD', array('case', 'do', 'done',
'elif', 'else', 'esac', 'fi', 'for', 'function', 'if', 'in', 'select',
'then', 'time', 'until', 'while',
'foreach', 'end' // zsh I think
));
// I could ls /usr/bin, but i think this will do for now
$this->add_identifier_mapping('FUNCTION', array('adduser', 'addgroup',
'alias', 'apropos', 'apt-get', 'aptitude', 'aspell', 'awk', 'basename', 'bash',
'bc', 'bg', 'break', 'builtin', 'bzip2', 'cal', 'case', 'cat', 'cd', 'cfdisk',
'chgrp', 'chmod', 'chown', 'chroot', 'chkconfig', 'cksum', 'clear', 'cmp',
'comm', 'command', 'continue', 'cp', 'cron', 'crontab', 'csplit', 'cut', 'date',
'dc', 'dd', 'ddrescue', 'declare', 'df', 'diff', 'diff3', 'dig', 'dir',
'dircolors', 'dirname', 'dirs', 'dmesg', 'du', 'echo', 'egrep', 'eject',
'enable', 'env', 'ethtool', 'eval', 'exec', 'exit', 'expect', 'expand',
'export', 'expr', 'false', 'fdformat', 'fdisk', 'fg', 'fgrep', 'file', 'find',
'fmt', 'fold', 'for', 'format', 'free', 'fsck', 'ftp', 'function', 'fuser',
'gawk', 'getopts', 'git', 'grep', 'groups', 'gzip', 'hash', 'head', 'help',
'history', 'hg', 'hostname', 'iconv', 'id', 'if', 'ifconfig', 'ifdown', 'ifup',
'import', 'install', 'jobs', 'join', 'kill', 'killall', 'less', 'let', 'ln',
'local', 'locate', 'logname', 'logout', 'look', 'lpc', 'lpr', 'lprint',
'lprintd', 'lprintq', 'lprm', 'ls', 'lsof', 'make', 'man', 'mkdir', 'mkfifo',
'mkisofs', 'mknod', 'more', 'mount', 'mtools', 'mtr', 'mv', 'mmv', 'nano',
'netstat', 'nice', 'nl', 'nohup', 'notify-send', 'nslookup', 'open', 'op',
'passwd', 'paste', 'pathchk', 'ping', 'pkill', 'popd', 'pr', 'printcap',
'printenv', 'printf', 'ps', 'pushd', 'pwd', 'quota', 'quotacheck', 'quotactl',
'ram', 'rcp', 'read', 'readarray', 'readonly', 'reboot', 'rename', 'renice',
'remsync', 'return', 'rev', 'rm', 'rmdir', 'rsync', 'screen', 'scp', 'sdiff',
'sed', 'select', 'seq', 'set', 'sftp', 'shift', 'shopt', 'shutdown', 'sleep',
'slocate', 'sort', 'source', 'split', 'ssh', 'strace', 'su', 'sudo', 'sum',
'suspend', 'svn', 'symlink', 'sync', 'tail', 'tar', 'tee', 'test', 'time',
'times', 'touch', 'top', 'traceroute', 'trap', 'tr', 'true', 'tsort', 'tty',
'type', 'ulimit', 'umask', 'umount', 'unalias', 'uname', 'unexpand', 'uniq',
'units', 'unset', 'unshar', 'until', 'useradd', 'usermod', 'users', 'uuencode',
'uudecode', 'v', 'vdir', 'vi', 'vim', 'vmstat', 'watch', 'wc', 'whereis',
'which', 'while', 'who', 'whoami', 'Wget', 'write', 'xargs', 'xdg-open',
'yes',));
$this->remove_stream_filter('oo-syntax');
$this->remove_filter('comment-to-doc');
$this->add_filter('str-filter', 'STRING', array($this, 'string_filter'));
}
function main() {
$stack = array();
while(!$this->eos()) {
$c = $this->peek();
// double brackets are apparently an arithemtic operation
// http://stackoverflow.com/questions/2188199/bash-double-or-single-bracket-parentheses-curly-braces
// anyway, if we apply normal highlighting in them, it seems to break some
// things
// TODO I think we should respect certain sub-types in the (( ... ))
// block, like strings and other stuff. It may require re-factoring the
// scanner to get this right. It may be best to switch this to a
// LumiousStatefulScanner. This will do for now though.
if ($this->scan('/(\\$?)(\({2})/')) {
$dollar = $this->match_group(1);
$this->record($this->match(), $dollar? 'KEYWORD' : null);
if ($this->scan_until('/\){2}/') !== null) {
$this->record($this->match(), null);
$this->record($this->scan('/\){2}/'), $dollar? 'KEYWORD' : null);
} else {
$this->record($this->rest(), null);
$this->terminate();
}
}
if ($this->scan('/\\$([{(])/')) {
$this->record($this->match(), 'KEYWORD');
$stack[] = array($this->match_group(1), true);
}
elseif($c === '[') {
$this->record($this->get(), 'KEYWORD');
$stack[] = array($c, true);
}
elseif ($c === '{' || $c === '(') {
$this->record($this->get(), null);
$stack[] = array($c, false);
}
elseif($c === '}' || $c === ')' || $c ===']') {
$match = array('{'=>'}', '('=>')', '[' => ']');
$type = null;
if (isset($stack[0])) {
$pop = array_pop($stack);
if ($pop[1]) $type = 'KEYWORD';
if ($match[$pop[0]] !== $c) {
// err
$stack[] = $pop;
$type = null;
}
}
$this->record($this->get(), $type);
if (empty($stack) && $this->interpolated) {
break;
}
}
elseif($c === '`') {
$this->record($this->get(), 'KEYWORD');
}
elseif ($this->scan('/
\$( [_a-zA-Z]\w* | [\d\#*@\-!_\\?\\$])
/xm')
) {
$this->record($this->match(), 'VARIABLE');
}
elseif($this->scan('/^(\s*)([_a-zA-Z]\w*(?=[=]))/m')) {
$m = $this->match_groups();
if ($m[1] !== '') $this->record($m[1], null);
$this->record($m[2], 'VARIABLE');
}
elseif (($this->interpolated && count($stack) === 1 &&
$this->scan('/(?<=\s|^)\#.*?(?=[)]|$)/m'))
|| $this->scan('/(?<=\s|^)\#.*/')) {
$this->record($this->match(), 'COMMENT');
}
elseif(($m = $this->scan("/\\$?'(?> [^'\\\\]+ | \\\\.)* '/sx"))) {
$tok = ($m[0] === '$')? 'VARIABLE' : 'STRING';
$this->record($m, $tok);
}
elseif($this->scan('/-*[a-zA-Z_][\-\w]*/')) {
$this->record($this->match(), 'IDENT');
}
// quoted heredoc is the same as a single string, no interpolation,
// A straight regex is causing backtracking problems on my box so
// we're going to do it the hard way
// note that the <<- means the delimiter can be indented.
elseif($this->scan('/(<<-?)(\s*)(["\'])(\w+)((?:\\3)?)/msx')) {
$m = $this->match_groups();
$this->record($m[1] . $m[2], null);
$this->record($m[3] . $m[4] . $m[5], 'DELIMITER');
$delim_regex = "/^(" . (($m[1] === '<<-')? '\s*' : '')
. ')(' . preg_quote($m[4], '/') . ')\\b/m';
$heredoc = $this->scan_until($delim_regex);
if ($heredoc === null) {
$heredoc = $this->rest();
$this->terminate();
}
$this->record($heredoc, 'HEREDOC');
if ($this->scan($delim_regex) !== null) {
$g = $this->match_groups();
if ($g[1] !== '') $this->record($g[1], null);
$this->record($g[2], 'DELIMITER');
}
}
// heredocs and double quoted strings are pretty much the same
elseif($this->scan('/(<<-?\s*)(\w+)/') ||
$this->scan('/\\$?"/'))
{
$pos = $this->match_pos();
$m = $this->match_groups();
$type = 'STRING';
$delim = '';
if ($m[0][0] === '<') {
$type = 'HEREDOC';
$this->record($m[1], null);
$this->record($m[2], 'KEYWORD');
$delim = $m[2];
if ($m[0][2] === '-') $delim = "[ \t]*" . $delim;
$pos = $this->pos();
}
elseif($m[0][0] === '$') $type = 'VARIABLE';
$in_str = true;
$searches = array(($type === 'HEREDOC')? "/^$delim\\b/m" :
'/(?<!\\\\)((?:\\\\\\\\)*)(")/',
'/(?<!\\\\)((?:\\\\\\\\)*)(\\$\\()/');
while(1) {
list($index, $matches) = $this->get_next($searches);
if ($index === -1) {
$this->record(substr($this->string(), $pos), $type);
$this->terminate();
break;
}
$hit = isset($matches[2])? $matches[2] : $matches[0];
$index_ = $index + strlen($matches[0]);
if($hit === '"') {
$this->record(substr($this->string(), $pos, $index_ - $pos), $type);
$this->pos($index_);
break;
}
// URGH WORST CHECK EVER.
elseif($type === 'HEREDOC' && !isset($matches[2])) {
$this->record(substr($this->string(), $pos, $index-$pos), $type);
$this->record($hit, 'KEYWORD');
$this->pos($index_);
break;
}
else {
$index_ = $index + strlen($matches[1]);
$this->record(substr($this->string(), $pos, $index_-$pos), $type);
$child = new LuminousBashScanner($this->string());
$child->pos($index_);
$child->interpolated = true;
$child->init();
$child->main();
$this->record($child->tagged(), 'INTERPOLATED', true);
$pos = $child->pos();
$this->pos($pos);
}
}
}
elseif($this->scan('/\d*[<>]+&?\d*/')) {
$this->record($this->match(), 'KEYWORD');
}
elseif($this->scan("/[^_\-a-zA-Z$'\"\#\{\}\(\)\[\]<>&\d`\n]+/") !== null) {
$this->record($this->match(), null);
}
else
$this->record($this->get(), null);
}
}
public static function guess_language($src, $info) {
$p = 0.0;
if (preg_match('%\\b (?:bash|csh|ksh|zsh|sh) \\b%x',
$info['shebang'])
)
return 1.0;
// strange conditional syntax -- if [ -z ... ]
if (preg_match('/ (if|while) \s++ \\[\s++-\w/x', $src)) $p += 0.10;
// quoted vars used in comparison: if [ "$somevar" ...
if (preg_match('/"\\$\w++"/', $src)) $p += 0.05;
// case ... esac has to be worth something
if (strpos($src, 'case') < strpos($src, 'esac')) $p += 0.1;
return $p;
}
}

View file

@ -0,0 +1,95 @@
<?php
/*
* BNF has a lot of different variants and matching them all is pretty much
* impossible.
*
* We're going to match the standard BNF and extended BNF and hopefully a
* few very similar dialects
*/
class LuminousBNFScanner extends LuminousStatefulScanner {
function user_def_ext($matches) {
if ($matches[1] !== '')
$this->record($matches[1], null);
$this->record_token($matches[2], 'USER_FUNCTION');
$this->user_defs[$matches[2]] = 'VALUE';
$this->pos_shift(strlen($matches[1]) + strlen($matches[2]));
}
private function set_strict() {
// no transition table necessary, I think
$this->add_pattern('COMMENT', '/<![^>]*>/');
$this->add_pattern('KEYWORD', '/(?<=^<)[^>]+(?=>)/m');
$this->add_pattern('KEYWORD', '/(?<=^\\{)[^\\}]+(?=\\})/m');
$this->add_pattern('VALUE', '/(?<=\\{)[^\\}]+(?=\\})/');
$this->add_pattern('VALUE', '/[\\-\w]+/');
}
private function set_extended() {
$this->add_pattern('COMMENT', '/\\(\\* .*? \\*\\)/sx');
$this->add_pattern('OPTION', '/\\[/', '/\\]/');
$this->add_pattern('REPETITION', '/\\{/', '/\\}/');
$this->add_pattern('GROUP', '/\\(/', '/\\)/');
$this->add_pattern('SPECIAL', '/\\?/', '/\\?/');
$ident = '(?:[\w\\-]+)';
$this->add_pattern('RULE', "/(^[ \t]*)($ident)(\s*(?![[:alnum:]\s]))/mi");
$this->overrides['RULE'] = array($this, 'user_def_ext');
$this->add_pattern('IDENT', "/$ident/");
// technically I don't know if we really need to worry about a transition
// table, but here we are anyway
$all = array('COMMENT', 'OPTION', 'REPETITION', 'GROUP', 'SPECIAL',
'STRING', 'IDENT', 'OPERATOR');
$almost_all = array_filter($all, create_function('$x',
'return $x !== "SPECIAL";'));
$this->transitions = array(
'initial' => array_merge(array('RULE'), $all),
'OPTION' => $all,
'REPETITION' => $all,
'GROUP' => $all,
'SPECIAL' => $almost_all
);
$this->rule_tag_map = array(
'OPTION' => null,
'REPETITION' => null,
'GROUP' => null,
'SPECIAL' => null
);
}
function init() {
// the original BNF uses <angle brackets> to delimit its
// production rule names
if (preg_match('/<\w+>/', $this->string())) {
$this->set_strict();
}
else {
$this->set_extended();
}
$this->add_pattern('STRING', LuminousTokenPresets::$SINGLE_STR_SL);
$this->add_pattern('STRING', LuminousTokenPresets::$DOUBLE_STR_SL);
$this->add_pattern('OPERATOR', '/[*\\-=+;:\\|,]+/');
// assume a few chars at bol indicate a commented line
$this->add_pattern('COMMENT', '/^[!%-;].*/m');
$this->remove_filter('constant');
$this->remove_filter('comment-to-doc');
}
static function guess_language($src, $info) {
// being honest, BNF is going to be so rare that if we ever return
// anything other than 0, it's more likely that we're obscuring the
// correct scanner than correctly identifying BNF.
return 0;
}
}

View file

@ -0,0 +1,132 @@
<?php
require_once(dirname(__FILE__) . '/include/c_func_list.php');
// TODO: trigraph... does anyone use these?
class LuminousCppScanner extends LuminousSimpleScanner {
function __construct($src=null) {
parent::__construct($src);
$this->add_filter('preprocessor', 'PREPROCESSOR',
array($this, 'preprocessor_filter'));
$this->add_identifier_mapping('FUNCTION',
$GLOBALS['luminous_c_funcs']);
$this->add_identifier_mapping('KEYWORD',
$GLOBALS['luminous_c_keywords']);
$this->add_identifier_mapping('TYPE',
$GLOBALS['luminous_c_types']);
}
function init() {
// http://www.lysator.liu.se/c/ANSI-C-grammar-l.html
// D [0-9]
// L [a-zA-Z_]
// H [a-fA-F0-9]
// E [Ee][+-]?{D}+
// FS (f|F|l|L)
// IS (u|U|l|L)*//
// {L}({L}|{D})* ident
// 0[xX]{H}+{IS}? hex
// 0{D}+{IS}? octal
// {D}+{IS}? int
// L?'(\\.|[^\\'])+' char
// {D}+{E}{FS}? real/float
// {D}*"."{D}+({E})?{FS}? real/float
// {D}+"."{D}*({E})?{FS}? real/float
// L?\"(\\.|[^\\"])*\" string, but we should exclude nl
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_ML);
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_SL);
$this->add_pattern('STRING', "/L?\"(?: [^\\\\\"\n]+ | \\\\.)*(?:$|\")/xms");
// if memory serves, a char looks like this:
$this->add_pattern('CHARACTER',
"/L? ' (?: \\\\(?: x[A-F0-9]{1,2}| . ) | . ) (?: '|$)/ixm");
$this->add_pattern('OPERATOR', '@[!%^&*\-/+=~:?.|<>]+@');
$this->add_pattern('NUMERIC', '/0[xX][A-F0-9]+[uUlL]*/i');
$this->add_pattern('NUMERIC', '/
(?:
(?: \d* \.\d+ | \d+\.\d*)
([eE][+-]?\d+)?
([fFlL]?)
)
/ix');
$this->add_pattern('NUMERIC', '/
\d+([uUlL]+ | ([eE][+-]?\d+)?[fFlL]? | ) #empty string on the end
/x'); //inc octal
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_HEX);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_REAL);
$this->add_pattern('PREPROCESSOR', '/^[ \t]*\#/m');
$this->add_pattern('IDENT', '/[a-zA-Z_]+\w*/');
$this->overrides['PREPROCESSOR'] = array($this, 'preprocessor_override');
}
function preprocessor_override() {
$this->skip_whitespace();
// #if 0s nest, according to Kate, which sounds reasonable
$pattern = '/^\s*#\s*if\s+0\\b/m';
if($this->check($pattern)) {
$this->nestable_token('COMMENT', '/^\s*#\s*if(?:n?def)?\\b/m',
'/^\s*#\s*endif\\b/m');
}
else {
// a preprocessor statement may have nested comments and strings. We
// go the lazy route and just zap the whole thing with a regex and let a
// filter figure out any nested highlighting
$this->scan("@ \#
(?: [^/\n\\\\]+
| /\* (?> [^\\*]+ | (?:\*(?!/))+ ) (?: $|\*/) # nested ML comment
| //.* # nested SL comment
| /
| \\\\(?s:.) # escape, and newline
)* @x");
$this->record($this->match(), 'PREPROCESSOR');
}
}
static function preprocessor_filter_cb($matches) {
if (!isset($matches[0]) || !isset($matches[0][0]))
return ''; // shouldn't ever happen
if ($matches[0][0] === '"') return LuminousUtils::tag_block('STRING', $matches[0]);
else if ($matches[0][0] === '&')
return '&lt;' . LuminousUtils::tag_block('STRING', $matches[1]) . '&gt;';
else return LuminousUtils::tag_block('COMMENT', $matches[0]);
}
static function preprocessor_filter($token) {
$token = LuminousUtils::escape_token($token);
$token[1] = preg_replace_callback("@
(?:\" (?> [^\\\\\n\"]+ | \\\\. )* (?: \"|$) | (?: &lt; (.*?) &gt;))
| // .*
| /\* (?s:.*?) (\*/ | $)
@x",
array('LuminousCppScanner', 'preprocessor_filter_cb'),
$token[1]);
return $token;
}
static function guess_language($src, $info) {
// Obviously, C tends to look an awful lot like pretty much every other
// language. Its only real pseudo-distinct feature is the ugly
// preprocessor and "char * ", so let's go with that
$p = 0.0;
if (preg_match('/^\s*+#\s*+(include\s++[<"]|ifdef|endif|define)\\b/m',
$src)
)
$p += 0.3;
if (preg_match('/\\bchar\s*\\*\s*\w+/', $src)) $p += 0.05;
if (preg_match('/\\bmalloc\s*\\(/', $src)) $p += 0.02;
// TODO we could guess at some C++ stuff too
return $p;
}
}

View file

@ -0,0 +1,52 @@
<?php
class LuminousCSharpScanner extends LuminousSimpleScanner {
public function init() {
$this->add_pattern('PREPROCESSOR', "/\\#(?: [^\\\\\n]+ | \\\\. )*/sx");
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_SL);
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_ML);
$this->add_pattern('STRING', LuminousTokenPresets::$DOUBLE_STR);
$this->add_pattern('CHARACTER', LuminousTokenPresets::$SINGLE_STR);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_HEX);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_REAL);
$this->add_pattern('IDENT', '/[a-z_]\w+/i');
$this->add_pattern('OPERATOR', '/[¬!%^&*\-=+~|?\\/><;:.,]+/i');
$this->add_identifier_mapping('KEYWORD', array('abstract', 'as', 'base',
'break', 'case', 'catch', 'checked', 'class', 'continue', 'default',
'delegate', 'do', 'event', 'explicit', 'extern', 'else', 'finally',
'false', 'fixed', 'for', 'foreach', 'goto', 'if', 'implicit', 'in',
'interface', 'internal', 'is', 'lock', 'new', 'null', 'namespace',
'operator', 'out', 'override', 'params', 'private',
'protected', 'public', 'readonly', 'ref', 'return', 'struct', 'switch',
'sealed', 'sizeof', 'stackalloc', 'static', 'this', 'throw', 'true',
'try', 'typeof', 'unchecked', 'unsafe', 'using', 'var', 'virtual',
'volatile', 'while', 'yield'));
require(dirname(__FILE__) . '/include/csharp_list.php');
$this->add_identifier_mapping('TYPE', array_merge(array(
// primatives
'bool', 'byte', 'char',
'const', 'double', 'decimal', 'enum', 'float', 'int', 'long',
'object',
'sbyte', 'short', 'string', 'uint', 'ulong', 'ushort',
'void'
),
$luminous_csharp_type_list)
);
}
static function guess_language($src, $info) {
$p = 0.0;
if (preg_match('/^\s*#region\\b/m', $src)) $p += 0.10;
if (preg_match('/^\s*using\s+System;/m', $src)) $p += 0.10;
if (preg_match('/^\s*using\s+System\\..*;/m', $src)) $p += 0.10;
if (preg_match('/partial\s+class\s+\w+/', $src)) $p += 0.05;
return $p;
}
}

View file

@ -0,0 +1,190 @@
<?php
/**
* CSS scanner.
* TODO: it would be nice if we could extend this somehow to handle
* CSS dialects which allow rule nesting.
*/
class LuminousCSSScanner extends LuminousEmbeddedWebScript {
private $expecting;
function __construct($src=null) {
parent::__construct($src);
$this->rule_tag_map = array(
'TAG' => 'KEYWORD',
'KEY' => 'TYPE',
'SELECTOR' => 'VARIABLE',
'ATTR_SELECTOR' => 'OPERATOR',
'SSTRING' => 'STRING',
'DSTRING' => 'STRING',
'ROUND_BRACKET_SELECTOR' => 'OPERATOR',
);
$this->dirty_exit_recovery = array(
'COMMENT' => '%.*?(?:\*/|$)%s',
'SSTRING' => "/(?:[^\\\\']+|\\\\.)*(?:'|$)/",
'DSTRING' => '/(?:[^\\\\"]+|\\\\.)*(?:"|$)/',
'ATTR_SELECTOR' => '/(?: [^\\]\\\\]+ | \\\\.)* (?:\]|$)/xs',
'ROUND_BRACKET_SELECTOR' => '/(?: [^\\)\\\\]+ | \\\\.)* (?:\)|$)/xs',
);
$this->state_ [] = 'global';
}
function init() {
$this->expecting = null;
}
function main() {
$comment_regex = '% /\* .*? \*/ %sx';
$this->start();
while (!$this->eos()) {
if (!$this->clean_exit) {
try {
$tok = $this->resume();
if ($this->server_break($tok)) break;
$this->record($this->match(), $tok);
} catch(Exception $e) {
if (LUMINOUS_DEBUG) throw $e;
else continue;
}
}
$this->skip_whitespace();
$pos = $this->pos();
$tok = null;
$m = null;
$state = $this->state();
$in_block = $state === 'block';
$in_media = $state === 'media';
$get = false;
$c = $this->peek();
if ($this->embedded_server && $this->check($this->server_tags)) {
$this->interrupt = true;
$this->clean_exit = true;
break;
}
elseif ($c === '/' && $this->scan(LuminousTokenPresets::$C_COMMENT_ML))
$tok = 'COMMENT';
elseif($in_block && $c === '#' &&
$this->scan('/#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?/'))
$tok = 'NUMERIC';
elseif($in_block && (ctype_digit($c) || $c === '-')
&& $this->scan('/-?(?>\d+)(\.(?>\d+))?(?:em|px|ex|ch|mm|cm|in|pt|%)?/')
!== null) {
$tok = 'NUMERIC';
}
elseif(!$in_block && $this->scan('/(?<=[#\.:])[\w\-]+/') !== null)
$tok = 'SELECTOR';
// check for valid super-blocks, e.g. media {...} and @keyframes {}
elseif(!$in_block && !$in_media && $c === '@'
&& $this->scan('/@
(-(moz|ms|webkit|o)-)?keyframes\\b
|
media\\b/x')
) {
$this->state_[] = 'media';
$tok = 'TAG';
}
elseif(( ctype_alpha($c) || $c === '!' || $c === '@' || $c === '_' || $c === '-' )
&& $this->scan('/(!?)[\-\w@]+/')) {
if ($in_media) $tok = 'VALUE';
elseif (!$in_block || $this->match_group(1) !== '') $tok = 'TAG';
elseif($this->expecting === 'key') $tok = 'KEY';
elseif($this->expecting === 'value') {
$m = $this->match();
if ($m === 'url' || $m === 'rgb' || $m === 'rgba') $tok = 'FUNCTION';
else $tok = 'VALUE';
}
}
// TODO attr selectors should handle embedded strings, I think.
elseif(!$in_block && $c === '['
&& $this->scan('/\[ (?> [^\\]\\\\]+ | \\\\.)* \]/sx'))
$tok = 'ATTR_SELECTOR';
elseif(!$in_block && $c === '('
&& $this->scan('/\( (?> [^\\)\\\\]+ | \\\\.)* \) /sx')) {
$tok = 'ROUND_BRACKET_SELECTOR';
}
elseif($c === '}' || $c === '{') {
$get = true;
if ($c === '}' && ($in_block || $in_media)) {
array_pop($this->state_);
if ($in_media) {
// @media adds a 'media' state, then the '{' begins a new global state.
// We've just popped global, now we need to pop media.
array_pop($this->state_);
}
}
elseif (!$in_block && $c === '{') {
if ($in_media) {
$this->state_[] = 'global';
}
else {
$this->state_[] = 'block';
$this->expecting = 'key';
}
}
}
elseif($c === '"' && $this->scan(LuminousTokenPresets::$DOUBLE_STR))
$tok = 'DSTRING';
elseif($c === "'" && $this->scan(LuminousTokenPresets::$SINGLE_STR))
$tok = 'SSTRING';
elseif($c === ':' && $in_block) {
$this->expecting = 'value';
$get = true;
$tok = 'OPERATOR';
}
elseif($c=== ';' && $in_block) {
$this->expecting = 'key';
$get = true;
$tok = 'OPERATOR';
}
elseif($this->embedded_html && $this->check('%<\s*/\s*style%i')) {
$this->interrupt = false;
$this->clean_exit = true;
break;
}
elseif($this->scan('/[:\\.#>*]+/')) $tok = 'OPERATOR';
else {
$get = true;
}
if ($this->server_break($tok)) break;
$m = $get? $this->get() : $this->match();
$this->record($m, $tok);
assert($this->pos() > $pos || $this->eos());
}
}
public static function guess_language($src, $info) {
$p = 0;
if (preg_match(
"/(font-family|font-style|font-weight)\s*+:\s*+[^;\n\r]*+;/", $src))
$p += 0.15;
if (strpos($src, '!important') !== false) $p += 0.05;
// generic rule
if (preg_match("/\\b(div|span|table|body)\\b [^\n\r\{]*+ [\r\n]*+ \{/x",
$src))
$p += 0.10;
return $p;
}
}

View file

@ -0,0 +1,208 @@
<?php
/*
* Diff is a strange one because we could just highlight the lines and be done
* with it, but we are actually going to try to highlight the source code AND
* the diff format
*
* As such, we handle formatting and tagging inside the scanner.
*/
class LuminousDiffScanner extends LuminousScanner {
public $patterns = array();
public $pretty_mode = false; // pretty mode uses language sub-scanners
// to try to highlight the embedded code
/* TODO: plug this into the language code selector in the old EasyAPI
* when we port it across
* This function is just a placeholder and will be implemented properly
* later.
*/
function get_child_scanner($filename) {
// HACK - pretty mode should be reflected elsewhere than here.
if (!$this->pretty_mode) return null;
// $luminous_ is a singleton from the main calling API. It may or may not
// exist here, but if it does, we're going to use it.
global $luminous_;
if (!isset($luminous_))
return null;
$spos = strrpos($filename, '.');
if ($spos === false) {return null;}
$ext = substr($filename, $spos+1);
$s = $luminous_->scanners->GetScanner(strtolower($ext));
// we actually only want the classname, not an instance.
if ($s === null) return null;
else return get_class($s);
}
function string($string=null) {
if ($string !== null) {
if (preg_match('/^[><]/m', $string)) {
// normal rules
$this->patterns['range'] = '/\d+.*/';
$this->patterns['codeblock'] = "/(^([<> ]).*(\n)?)+/m";
}
elseif (preg_match('/^\*{3}/m', $string)) {
// context
$this->patterns['range'] = "/([\-\*]{3})[ \t]+\d+,\d+[ \t]+\\1.*/";
$this->patterns['codeblock'] = "/(^([!+ ]).*(\n)?)+/m";
}
else {
// unified
$this->patterns['range'] = "/@@.*/";
$this->patterns['codeblock'] = "/(^([+\- ]).*(\n)?)+/m";
}
}
return parent::string($string);
}
function main() {
// we're aiming to handle context, unified and normal diff all at once here
// because it doesn't really seem that hard.
$child = null;
$last_index = -1;
while (!$this->eos()) {
$index = $this->pos();
assert($index > $last_index);
$last_index = $index;
assert($this->bol());
$tok = null;
if ($this->scan('/diff\s.*$/m') !== null) $tok = 'KEYWORD';
// normal, context and unified ranges
elseif($this->scan($this->patterns['range']) !== null)
$tok = 'DIFF_RANGE';
elseif($this->scan("/-{3}[ \t]*$/m")) $tok = null;
elseif($this->scan('/(?:\**|=*|\w.*)$/m') !== null) $tok = 'KEYWORD';
// this is a header line which may contain a file path. If it does,
// update the child scanner according to its extension.
elseif($this->scan("@[+\-\*]{3}(\s+([^\s]*)([ \t]|$))?.*@m") !== null) {
$m = $this->match_groups();
// unified uses +++, context uses *
if ($m[0][0] === '+' || $m[0][0] === '*')
$tok = 'DIFF_HEADER_NEW';
else $tok = 'DIFF_HEADER_OLD';
if (isset($m[2])) {
$filename = preg_replace('@.*\\\\/@', '', $m[2]);
$child = self::get_child_scanner($filename);
}
}
elseif($this->scan('/\\\\.*/') !== null) $tok = null;
elseif($this->scan($this->patterns['codeblock']) !== null) {
// this is actual source code.
// we're going to format this here.
// we're going to extract the block, and try to re-assemble it as
// verbatim code, then highlight it via a child scanner, then split up
// the lines, re-apply the necessary prefixes (e.g. + or -) to them,
// and store them as being a DIFF_ token.
// we have to do it like this, rather than line by line, otherwise
// multiline tokens aren't going to work properly. There's stilla risk
// that the diff will be fragmented such the child scanner gets it
// wrong but that can't be helped.
// TODO restructure this so the complicated bits aren't done if there's
// no child scanner to pass it down to
$block = $this->match();
if (!strlen($block)) {
assert(0);
}
$lines = explode("\n", $block);
$verbatim = array();
$verbatim_ = '';
$types = array();
$prefixes = array();
foreach($lines as $l) {
if (!strlen($l) || $l[0] === ' ')
$types[]= 'DIFF_UNCHANGED';
elseif ($l[0] === '+' || $l[0] === '>')
$types[] = 'DIFF_NEW';
elseif ($l[0] === '!' || $l[0] === '<' || $l[0] === '-')
$types[] = 'DIFF_OLD';
else assert(0);
$prefixes[] = (isset($l[0]))? $l[0] : '';
$verbatim_[] = substr($l, 1);
}
$verbatim = implode("\n", $verbatim_);
$escaped = false;
$tagged;
if ($child !== null) {
$c = new $child;
$c->init();
$c->string($verbatim);
$c->main();
$tagged = $c->tagged();
$escaped = true;
} else {
$tagged = $verbatim;
}
$exp = explode("\n", $tagged);
assert(count($exp) === count($prefixes));
foreach($exp as $i=>$v) {
$t = $types[$i];
// if the sub-scanner escaped the line, we also need to escape the
// prefix for consistency
$prefix = $prefixes[$i];
if ($escaped) $prefix = LuminousUtils::escape_string($prefix);
$text = $prefix . $v;
$this->record(
$text,
$t,
$escaped);
if ($i < count($exp)-1) $this->record("\n", null);
}
if ($this->eol()) $this->record($this->get(), null);
continue;
}
else $this->scan('/.*/');
// previous else clause can capture empty strings
if ($this->match() !== '')
$this->record($this->match(), $tok);
assert($this->eol());
// consume newline
if (!$this->eos()) $this->record($this->get(), null);
}
}
static function guess_language($src, $info) {
// diff isn't too hard. We check for 'index' and a few other things
$p = 0.0;
if (preg_match("/^-{3}.*+[\n\r]++\\+{3}/m", $src)) $p = 0.25;
if (preg_match('/^@@.*@@/m', $src)) $p += 0.25;
if (preg_match('/^(index|diff)\\b/m', $src)) $p += 0.10;
// finally we look for the diff markers at the line starts
// we're going to use the remaining 40% of the probability as so:
// We'll say a perfect match for diff has
// 10%+ of its lines starting with the +/- markers (</> or +/! for
// context/original format), and we'll scale real proportion
// to fill up the remaining 0.4
$c = preg_match_all('/^[<>+\\-!]\s/m', $src, $m);
$num_lines = $info['num_lines'];
if ($num_lines > 0) {
$proportion = $c/$num_lines;
$proportion = min(0.1, $proportion);
$p += 0.4 * ($proportion * 10);
}
return $p;
}
}
class LuminousPrettyDiffScanner extends LuminousDiffScanner {
public $pretty_mode = true;
}

View file

@ -0,0 +1,257 @@
<?php
/**
* This is a rename of the JavaScript scanner.
* TODO Some of these things are JS specific and should be moved into
* the new JS scanner.
*/
class LuminousECMAScriptScanner extends LuminousEmbeddedWebScript {
public $script_tags = '</script>';
// regular expressions in JavaScript are delimited by '/', BUT, the slash
// character may appear unescaped within character classes
// we can handle this fairly easily with a single regex because the classes
// do not nest
// TODO:
// I do not know if this is specific to Javascript or ECMAScript derivatives
// as a whole, I also don't know if multi-line regexen are legal (i.e. when
// the definition spans multiple lines)
protected $regex_regex = "%
/
(?:
[^\\[\\\\/]+ # not slash, backslash, or [
| \\\\. # escape char
|
(?: # char class [..]
\\[
(?:
[^\\]\\\\]+ # not slash or ]
| \\\\. # escape
)*
(?: \\] | \$)
) # close char class
)*
(?: /[iogmx]* | \$) #delimiter or eof
%sx";
// logs a persistent token stream so that we can lookbehind to figure out
// operators vs regexes.
private $tokens_ = array();
private $child_state = null;
function __construct($src=null) {
$this->rule_tag_map = array(
'COMMENT_SL' => 'COMMENT',
'SSTRING' => 'STRING',
'DSTRING' => 'STRING',
'OPENER' => null,
'CLOSER' => null,
);
$this->dirty_exit_recovery = array(
'COMMENT_SL' => '/.*/',
'COMMENT' => '%.*?(\*/|$)%s',
'SSTRING' => "/(?:[^\\\\']+|\\\\.)*('|$)/",
'DSTRING' => '/(?:[^\\\\"]+|\\\\.)*("|$)/',
// FIXME: Anyone using a server-side interruption to build a regex is
// frankly insane, but we are wrong in the case that they were in a
// character class when the server language interrupted, and we may
// exit the regex prematurely with this
'REGEX' => '%(?:[^\\\\/]+|\\\\.)*(?:/[iogmx]*|$)%',
);
parent::__construct($src);
$this->add_identifier_mapping('KEYWORD', array('break', 'case', 'catch',
'comment', 'continue', 'do', 'default', 'delete', 'else', 'export',
'for', 'function', 'if', 'import', 'in', 'instanceof', 'label', 'new',
'null', 'return', 'switch', 'throw', 'try', 'typeof', 'var', 'void',
'while', 'with',
'true', 'false', 'this'
));
$this->add_identifier_mapping('FUNCTION', array('$', 'alert', 'confirm',
'clearTimeout', 'clearInterval',
'encodeURI', 'encodeURIComponent', 'eval', 'isFinite', 'isNaN',
'parseInt', 'parseFloat', 'prompt',
'setTimeout', 'setInterval',
'decodeURI', 'decodeURIComponent', 'jQuery'));
$this->add_identifier_mapping('TYPE', array('Array', 'Boolean', 'Date',
'Error', 'EvalError', 'Infinity', 'Image', 'Math', 'NaN', 'Number',
'Object', 'Option', 'RangeError', 'ReferenceError', 'RegExp', 'String',
'SyntaxError', 'TypeError', 'URIError',
'document',
'undefined', 'window'));
}
function is_operand() {
for ($i = count($this->tokens) -1 ; $i>= 0; $i--) {
$tok = $this->tokens[$i][0];
if ($tok === null || $tok === 'COMMENT' || $tok === 'COMMENT_SL') continue;
return ($tok === 'OPERATOR' || $tok === 'OPENER');
}
return true;
}
function init() {
if ($this->embedded_server)
$this->add_pattern('STOP_SERVER', $this->server_tags);
if ($this->embedded_html)
$this->add_pattern('STOP_SCRIPT', '%</script>%');
$op_pattern = '[=!+*%\-&^|~:?\;,.>';
if (!($this->embedded_server || $this->embedded_html))
$op_pattern .= '<]+';
else {
// build an alternation with a < followed by a lookahead
$op_pattern .= ']|<(?![';
// XXX this covers <? and <% but not very well
if ($this->embedded_server) $op_pattern .= '?%';
if ($this->embedded_html) $op_pattern .= '/';
$op_pattern .= '])'; // closes lookahead
$op_pattern = "(?:$op_pattern)+";
}
$op_pattern = "@$op_pattern@";
$this->add_pattern('IDENT', '/[a-zA-Z_$][_$\w]*/');
// NOTE: slash is a special case, and </ may be a script close
$this->add_pattern('OPERATOR', $op_pattern);
// we care about openers for figuring out where regular expressions are
$this->add_pattern('OPENER', '/[\[\{\(]+/');
$this->add_pattern('CLOSER', '/[\]\}\)]+/');
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_HEX);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_REAL);
$this->add_pattern('SSTRING', LuminousTokenPresets::$SINGLE_STR_SL);
$this->add_pattern('DSTRING', LuminousTokenPresets::$DOUBLE_STR_SL);
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_ML);
$this->add_pattern('COMMENT_SL', LuminousTokenPresets::$C_COMMENT_SL);
// special case
$this->add_pattern('SLASH', '%/%');
$stop_patterns = array();
$xml_scanner = new LuminousHTMLScanner($this->string());
$xml_scanner->xml_literal = true;
$xml_scanner->scripts = false;
$xml_scanner->embedded_server = $this->embedded_server;
if ($this->embedded_server)
$xml_scanner->server_tags = $this->server_tags;
$xml_scanner->init();
$xml_scanner->pos($this->pos());
$this->add_child_scanner('xml', $xml_scanner);
}
// c+p from HTML scanner
function scan_child($lang) {
assert (isset($this->child_scanners[$lang]));
$scanner = $this->child_scanners[$lang];
$scanner->pos($this->pos());
$substr = $scanner->main();
$this->record($scanner->tagged(), 'XML', true);
$this->pos($scanner->pos());
if ($scanner->interrupt) {
$this->child_state = array($lang, $this->pos());
} else {
$this->child_state = null;
}
}
function main() {
$this->start();
$this->interrupt = false;
while (!$this->eos()) {
$index = $this->pos();
$tok = null;
$m = null;
$escaped = false;
if (!$this->clean_exit) {
try {
$tok = $this->resume();
} catch(Exception $e) {
if (LUMINOUS_DEBUG) throw $e;
else {
$this->clean_exit = true;
continue;
}
}
}
elseif ($this->child_state !== null && $this->child_state[1] < $this->pos()) {
$this->scan_child($this->child_state[0]);
continue;
}
elseif (($rule = $this->next_match()) !== null) {
$tok = $rule[0];
if ($rule[1] > $index) {
$this->record(substr($this->string(), $index, $rule[1] - $index), null);
}
} else {
$this->record(substr($this->string(), $index), null);
$this->clean_exit = true;
$this->interrupt = false;
$this->terminate();
break;
}
if ($tok === 'SLASH') {
if ($this->is_operand()) {
$tok = 'REGEX';
$this->unscan();
assert($this->peek() === '/');
$m = $this->scan($this->regex_regex);
if ($m === null) {
assert(0);
$m = $this->rest();
$this->terminate();
}
} else {
$tok = 'OPERATOR';
}
}
elseif ($tok === 'OPERATOR' && $this->match() === '<') {
if ($this->is_operand()) {
$this->unscan();
$this->scan_child('xml');
continue;
}
}
elseif ($tok === 'STOP_SERVER') {
$this->interrupt = true;
$this->unscan();
break;
}
elseif ($tok === 'STOP_SCRIPT') {
$this->unscan();
break;
}
if ($m === null)
$m = $this->match();
if ($this->server_break($tok))
break;
if ($tok === 'COMMENT_SL' && $this->script_break($tok)
)
break;
assert($this->pos() > $index);
$tag = $tok;
$this->record($m, $tag, $escaped);
}
}
}

View file

@ -0,0 +1,163 @@
<?php
/*
* Erlang.
*
* Various comments refer to section numbers in the official spec, which can
* be found at http://www.erlang.org/download/erl_spec47.ps.gz
*/
class LuminousErlangScanner extends LuminousSimpleScanner {
// applies interpolation highlighting, can't find a proper
// reference for this though
static function str_filter($token) {
if (strpos($token[1], '~') == false) return $token;
$token = LuminousUtils::escape_token($token);
$token[1] = preg_replace('/~(?:\d+|.)/',
'<INTERPOLATION>$0</INTERPOLATION>', $token[1]);
return $token;
}
// helper function: generates a regex which matches only numeric strings
// in the given base
static function build_based_int_regex($base) {
assert(2 <= $base && $base <= 16);
$regex = '/(?i:[0-';
if ($base <= 10)
$regex .= (string)$base-1;
else
$regex .= '9a-' . strtolower(dechex($base-1));
$regex .= '])+/';
return $regex;
}
// 3.11 integers are pretty strange, you are allowed to specify base
// 2 ><= b <= 16 arbitrarily.
function based_int($matches) {
$base = $matches[1];
$match = $matches[0];
$this->pos_shift(strlen($matches[0]));
$number = null;
if ($base >= 2 && $base <= 16)
$number = $this->scan($this->build_based_int_regex((int)$base));
if ($number !== null) {
$match .= $number;
}
$this->record($match, 'NUMERIC');
// now we're going to greedily consume any trailing numbers
// This handles the case e.g. 2#001122,
// we don't want the '22' to get caught as a separate literal, we want to
// make sure it's NOT highlighted as a literal
// so we consume it here.
if ($this->scan('/\d+/') !== null) {
$this->record($this->match(), null);
}
}
static function oo_stream_filter($tokens) {
$c = count($tokens)-1;
for($i=0; $i<$c; $i++) {
if ($tokens[$i][1] === ':') {
if ($i > 0) {
$behind = &$tokens[$i-1][0];
if ($behind === 'IDENT') $behind = 'OBJ';
}
if ($i < $c-1) {
$ahead = &$tokens[$i+1][0];
if ($ahead === 'IDENT') $ahead = 'OO';
$i++;
}
}
}
return $tokens;
}
function init() {
$this->remove_stream_filter('oo-syntax');
$this->remove_filter('comment-to-doc');
$this->add_stream_filter('oo-syntax', array($this, 'oo_stream_filter'));
$this->add_filter('interpolation', 'STRING', array($this, 'str_filter'));
// 3.6 - technically should include the newline, but doesn't really matter
$this->add_pattern('COMMENT', '/%.*/');
// stuff like -module, -author
$this->add_pattern('KEYWORD', '/^-(?:[a-z_]\w*)\\b/m');
// 3.11 integer with radix
$this->add_pattern('BASED_INT', '/[+\\-]?(\d+)#/');
$this->overrides['BASED_INT'] = array($this, 'based_int');
// float
$this->add_pattern('NUMERIC', '/[+\\-]?\d+\.\d+([eE][+\\-]?\d+)?/');
// int
$this->add_pattern('NUMERIC', '/[+\\-]?\d+/');
// 3.7 defines some 'separators', included are . : | || ; , ? -> and #
// we'll capture these separately to operators
// and map it to a keyword, for lack of anything better
$this->add_pattern('SEPARATOR', '/\\|\\||->|[\\.:\\|;,?#]/');
$this->rule_tag_map['SEPARATOR'] = 'KEYWORD';
// 3.9
$this->add_pattern('OPERATOR', '%==|/=|=:=|=<|>=|\\+\\+|--|<-|[+\\-*=!<>/]%');
// 3.9 named ops
$this->add_identifier_mapping('OPERATOR', array('div', 'rem', 'or', 'xor',
'bor', 'bxor', 'bsl', 'bsr', 'and', 'band', 'not', 'bnot'));
// char literals occur after a '$'
$this->add_pattern('CHARACTER', '/\\$(?:(?:\\\\(?:\\^\w+|\d+|.))|.)/');
$this->add_pattern('STRING', LuminousTokenPresets::$DOUBLE_STR);
// this looks like a string, but is in fact an 'atom'
// we'll call it a value,
$this->add_pattern('VALUE', LuminousTokenPresets::$SINGLE_STR);
$this->add_pattern('IDENT', '/[a-z][@\w]*/');
$this->add_pattern('VARIABLE', '/[A-Z][@\w]*/');
// 3.8
$this->add_identifier_mapping('KEYWORD', array('after', 'begin', 'case',
'catch', 'cond', 'end', 'fun', 'if', 'let', 'of', 'query', 'receive',
'when',
// reserved, but undefined:
'all_true', 'some_true'
));
$this->add_identifier_mapping('VALUE', array('true', 'false'));
// from the BIF section
$this->add_identifier_mapping('FUNCTION', array(
'atom', 'binary', 'constant', 'float', 'integer', 'function', 'list',
'number', 'pid', 'port', 'reference', 'tuple', 'atom_to_list', 'list_to_atom',
'abs', 'float', 'float_to_list', 'integer_to_list', 'list_to_float',
'list_to_integer', 'round', 'trunc', 'binary_to_list', 'binary_to_term',
'concat_binary', 'list_to_binary', 'size', 'split_binary', 'term_to_binary',
'element', 'list_to_tuple', 'seteleemnt', 'size', 'tuple_to_list', 'hd',
'length', 't1', 'check_process-code', 'delete_module', 'load_module',
'preloaded', 'purge_module', 'module_loaded', 'apply', 'exit', 'group_leader',
'link', 'list_to_pid', 'pid_to_list', 'process_flag', 'process_info',
'processes', 'self', 'spawn', 'spawn_link', 'unlink', 'erase', 'get',
'get_keys', 'put', 'disconnect_node', 'get_cookie', 'halt', 'is_alive',
'monitor_node', 'node', 'nodes', 'processes', 'set_cookie', 'set_node',
'statistics', 'register', 'registered', 'unregister', 'whereis', 'open_port',
'port_close', 'port_info', 'ports', 'date', 'hash', 'make_ref', 'now', 'throw',
'time', 'acos', 'asin', 'atan', 'atan2', 'cos', 'cosh', 'exp', 'log', 'log10',
'pi', 'pow', 'sin', 'sinh', 'tan', 'tanh'));
}
static function guess_language($src, $info) {
$p = 0.0;
foreach(array('module', 'author', 'export', 'include') as $s) {
if (strpos($src, '-' . $s) !== false) $p += 0.02;
}
if (strpos($src, ' ++ ') !== false) $p += 0.01;
if (preg_match('/[a-zA-Z_]\w*#[a-zA-Z_]+/', $src)) $p += 0.05;
// doc comment
if (preg_match('/^%%/m', $src)) $p += 0.05;
return $p;
}
}

View file

@ -0,0 +1,73 @@
<?php
/*
* Go.
*
* http://golang.org/doc/go_spec.html
*
* TODO: the different string formats have different escape codes, need
* to override the generic filter to handle this
* also, if there's a standard library API list, that would be useful.
*
*/
class LuminousGoScanner extends LuminousSimpleScanner {
function type_override($matches) {
$this->record($matches[1], 'IDENT');
$this->record($matches[2], null);
$this->record($matches[3], 'USER_FUNCTION');
$this->pos_shift(strlen($matches[0]));
$this->user_defs[$matches[3]] = ($matches[1] === 'type')? 'TYPE'
: 'FUNCTION';
}
function init() {
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_ML);
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_SL);
$ident = '[\p{L}_][\p{L}\p{N}_]*';
// this should be unicode for letter (\p{L}) and number (\p{N})
$this->add_pattern('type', "/\\b(type|func)(\s+)($ident)/u");
$this->overrides['type'] = array($this, 'type_override');
$this->add_pattern('IDENT', "/$ident/u");
$this->add_pattern('OPERATOR', '/[+\\-\\*\\/%&\\|^<>&=!:\\.,;]+/');
$exp = '[eE][+-]?\d+';
// note the trailing i - which denotes imaginary literals
$this->add_pattern('NUMERIC',
"/(?:\d+\.\d*(?:$exp)?|\d+$exp|\.\d+(?:$exp)?)i?/");
$this->add_pattern('NUMERIC', '/(?:0(?:\d+|x[a-fA-F0-9]+)|\d+)i?/');
$this->add_pattern('CHARACTER',
"/'(?:\\\\(?:\d+|[uUxX][a-fA-F0-9]+|.)|.)'/u");
$this->add_pattern('STRING', LuminousTokenPresets::$DOUBLE_STR);
$this->add_pattern('STRING', '/`(?:[^`\\\\]+|\\\\.)*(?:`|$)/s');
$this->add_identifier_mapping('KEYWORD', array('break', 'case', 'chan',
'const', 'continue', 'default', 'defer', 'else', 'fallthrough', 'for',
'func', 'go', 'goto', 'if', 'import', 'interface', 'map', 'package',
'range', 'return', 'select', 'struct', 'switch', 'type', 'var'));
$this->add_identifier_mapping('TYPE', array('any', 'bool', 'byte',
'complex', 'complex64', 'complex128', 'int', 'int8', 'int16', 'int32',
'int64', 'float', 'float32', 'float64', 'string', 'struct',
'uint', 'uint8', 'uint16', 'uint32', 'uint64', 'uintptr'));
$this->add_identifier_mapping('VALUE', array('false', 'iota', 'true'));
// from the old luminous language file, don't know how sensible these are
$this->add_identifier_mapping('FUNCTION', array('append', 'cap', 'copy',
'cmplx', 'imag', 'len', 'make', 'new', 'panic', 'print', 'println',
'real', 'recover', 'sizeof'));
}
public static function guess_language($src, $info) {
$p = 0.0;
if (strpos($src, 'func ') !== false) $p += 0.02;
if (preg_match('/func\s*\\(\s*\w+\s*\\*\s*\w+/', $src)) $p += 0.05;
if (preg_match('/^package\s+\w+/', $src)) $p += 0.01;
if (preg_match('/type\s+\w+\s+struct\s*\\{/', $src)) $p += 0.03;
return $p;
}
}

View file

@ -0,0 +1,166 @@
<?php
/*
* Groovy is pretty much a cross between Python and Java.
* It inherits all of Java's stuff
* http://groovy.codehaus.org/jsr/spec/Chapter03Lexical.html
*/
require_once(dirname(__FILE__) . '/include/java_func_list.php');
class LuminousGroovyScanner extends LuminousSimpleScanner {
public $interpolation = false;
protected $brace_stack = 0;
function regex_override($match) {
assert($this->peek() === '/');
assert($match === array(0=>'/'));
$regex = false;
$i = count($this->tokens);
while ($i--) {
list($tok, $contents) = $this->tokens[$i];
if ($tok === 'COMMENT') continue;
elseif ($tok === 'OPERATOR') $regex = true;
elseif($tok !== null) $regex = false;
else {
$t = rtrim($contents);
if ($t === '') continue;
$c = $t[strlen($t)-1];
$regex = ($c === '(' || $c === '[' || $c === '{');
}
break;
}
if (!$regex) {
$this->record($this->get(), 'OPERATOR');
}
else {
$m = $this->scan('@ / (?: [^\\\\/]+ | \\\\. )* (?: /|$) @sx');
assert($m !== null);
$this->record($m, 'REGEX');
}
}
// string interpolation is complex and it nests, so we do that in here
function interp_string($m) {
// this should only be called for doubly quoted strings
// and triple-double quotes
//
// interpolation is betwee ${ ... }
$patterns = array('interp' => '/(?<!\\$)\\$\\{/');
$start = $this->pos();
if (preg_match('/^"""/', $m[0])) {
$patterns['term'] = '/"""/';
$this->pos_shift(3);
}
else {
assert(preg_match('/^"/', $m[0]));
$patterns['term'] = '/"/';
$this->pos_shift(1);
}
while (1) {
$p = $this->pos();
list($name, $index, $matches) = $this->get_next_named($patterns);
if ($name === null) {
// no matches, terminate
$this->record(substr($this->string(), $start), 'STRING');
$this->terminate();
break;
}
elseif($name === 'term') {
// end of the string
$range = $index + strlen($matches[0]);
$this->record(substr($this->string(),
$start, $range-$start), 'STRING');
$this->pos($range);
break;
} else {
// interpolation, handle this with a subscanner
$this->record(substr($this->string(), $start, $index-$start), 'STRING');
$this->record($matches[0], 'DELIMITER');
$subscanner = new LuminousGroovyScanner($this->string());
$subscanner->interpolation = true;
$subscanner->init();
$subscanner->pos($index + strlen($matches[0]));
$subscanner->main();
$tagged = $subscanner->tagged();
$this->record($tagged, 'INTERPOLATION', true);
$this->pos($subscanner->pos());
if ($this->scan('/\\}/')) $this->record($this->match(), 'DELIMITER');
$start = $this->pos();
}
assert($p < $this->pos());
}
}
// brace override halts scanning if the stack is empty and we hit a '}',
// this is for interpolated code, the top-level scanner doesn't bind to this
function brace($m) {
if ($m[0] === '{') $this->brace_stack++;
elseif($m[0] === '}') {
if ($this->brace_stack <= 0)
return true;
$this->brace_stack--;
}
else assert(0);
$this->record($m[0], null);
$this->pos_shift(strlen($m[0]));
}
function init() {
$this->add_identifier_mapping('KEYWORD',
$GLOBALS['luminous_java_keywords']);
$this->add_identifier_mapping('TYPE', $GLOBALS['luminous_java_types']);
$this->add_identifier_mapping('KEYWORD', array('any', 'as', 'def', 'in',
'with', 'do', 'strictfp',
'println'));
// C+P from python
// so it turns out this template isn't quite as readable as I hoped, but
// it's a triple string, e.g:
// "{3} (?: [^"\\]+ | ""[^"\\]+ | "[^"\\]+ | \\.)* (?: "{3}|$)
$triple_str_template = '%1$s{3} (?> [^%1$s\\\\]+ | %1$s%1$s[^%1$s\\\\]+ | %1$s[^%1$s\\\\]+ | \\\\. )* (?: %1$s{3}|$)';
$str_template = '%1$s (?> [^%1$s\\\\]+ | \\\\. )* (?: %1$s|$)';
$triple_dstr = sprintf($triple_str_template, '"');
$triple_sstr = sprintf($triple_str_template, "'");
$this->add_pattern('COMMENT', '/^#!.*/');
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_ML);
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_SL);
$this->add_pattern('INTERP_STRING', "/$triple_dstr/sx");
$this->add_pattern('STRING', "/$triple_sstr/xs");
$this->add_pattern('INTERP_STRING', LuminousTokenPresets::$DOUBLE_STR);
$this->overrides['INTERP_STRING'] = array($this, 'interp_string');
// differs from java:
$this->add_pattern('STRING', LuminousTokenPresets::$SINGLE_STR);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_HEX);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_REAL);
$this->add_pattern('IDENT', '/[a-zA-Z_]\w*/');
$this->add_pattern('OPERATOR', '/[~!%^&*\-=+:?|<>]+/');
$this->add_pattern('SLASH', '%/%');
$this->overrides['SLASH'] = array($this, 'regex_override');
if ($this->interpolation) {
$this->add_pattern('BRACE', '/[{}]/');
$this->overrides['BRACE'] = array($this, 'brace');
}
}
static function guess_language($src, $info) {
$p = 0.0;
if (preg_match('/\\bdef\s+\w+\s*=/', $src)) $p += 0.04;
if (preg_match('/println\s+[\'"\w]/', $src)) $p += 0.03;
// Flawed check for interpolation, might match after a string
// terminator
if (preg_match("/\"[^\"\n\r]*\\$\\{/", $src)) $p += 0.05;
// regex literal ~/regex/
if (preg_match('%~/%', $src)) $p += 0.05;
if (preg_match('/^import\s+groovy/m', $src)) $p += 0.2;
return $p;
}
}

View file

@ -0,0 +1,99 @@
<?php
// Haskell scanner.
// We do not yet support TemplateHaskell because it looks INSANE.
/*
* TODO: Some contextual awareness would be great, Kate seems to highlight
* things differently depending on whether they're in [..] or (...) blocks,
* but I don't understand Haskell enough to embark on that right now.
*
* It would also be nice to distinguish between some different classes of
* operator.
*/
require_once(dirname(__FILE__) . '/include/haskell.php');
class LuminousHaskellScanner extends LuminousSimpleScanner {
// handles comment nesting of multiline comments.
function comment_override() {
$this->nestable_token('COMMENT', '/\\{-/', '/-\\}/');
}
function init() {
// import from ./include/
global $luminous_haskell_functions;
global $luminous_haskell_types;
global $luminous_haskell_values;
global $luminous_haskell_keywords;
$this->add_identifier_mapping('KEYWORD', $luminous_haskell_keywords);
$this->add_identifier_mapping('TYPE', $luminous_haskell_types);
$this->add_identifier_mapping('FUNCTION', $luminous_haskell_functions);
$this->add_identifier_mapping('VALUE', $luminous_haskell_values);
// shebang
$this->add_pattern('COMMENT', '/^#!.*/');
// Refer to the sections in
// http://www.haskell.org/onlinereport/lexemes.html
// for the rules implemented here.
// 2.4
$this->add_pattern('TYPE', '/[A-Z][\'\w]*/');
$this->add_pattern('IDENT', '/[_a-z][\'\w]*/');
// http://www.haskell.org/onlinereport/prelude-index.html
$this->add_pattern('FUNCTION', '/
(?: !!|\\$!?|&&|\\|{1,2}|\\*{1,2}|\\+{1,2}|-(?!-)|\\.|\\/=?|<=?|==|=<<|>>?=?|\\^\\^? )
/x');
$op_chars = '\\+%^\\/\\*\\?#<>:;=@\\[\\]\\|\\\\~\\-!$@%&\\|=';
// ` is used to make a function call into an infix operator
// CRAZY OR WHAT.
$this->add_pattern('OPERATOR', '/`[^`]*`/');
// some kind of function, lambda, maybe.
$this->add_pattern('FUNCTION', "/\\\\(?![$op_chars])\S+/");
// Comments are hard!
// http://www.mail-archive.com/haskell@haskell.org/msg09019.html
// According to this, we can PROBABLY, get away with checking either side
// for non-operator chars followed by at least 2 dashes, but I could well
// be wrong. It'll do for now.
$this->add_pattern('COMMENT', "/(?<![$op_chars])---*(?![$op_chars]).*/");
// nested comments are easy!
$this->add_pattern('NESTED_COMMENT', '/\\{-/');
$this->overrides['NESTED_COMMENT'] = array($this, 'comment_override');
$this->rule_tag_map['NESTED_COMMENT'] = 'COMMENT';
$this->add_pattern('OPERATOR', "/[$op_chars]+/");
// FIXME: the char type is way more discriminating than this
$this->add_pattern('STRING', LuminousTokenPresets::$DOUBLE_STR_SL);
$this->add_pattern('CHARACTER', LuminousTokenPresets::$SINGLE_STR_SL);
// 2.5
$this->add_pattern('NUMERIC', '/
0[oO]\d+ #octal
|
0[xX][a-fA-F\d]+ #hex
|
# decimal and float can be done at once, according to the grammar
\d+ (?: (?:\.\d+)? (?: [eE][+-]? \d+))?
/x');
}
public static function guess_language($src, $info) {
$p = 0.0;
// comments
if (preg_match('/\\{-.*\\-}/', $src)) $p += 0.05;
// 'import qualified' seems pretty unique
if (preg_match('/^import\s+qualified/m', $src)) $p += 0.05;
// "data SomeType something ="
if (preg_match('/data\s+\w+\s+\w+\s*=/', $src)) $p += 0.05;
return $p;
}
}

View file

@ -0,0 +1,252 @@
<?php
class LuminousHTMLScanner extends LuminousEmbeddedWebScript {
private $child_state = null;
public $scripts = true;
// XML literals are part of several languages. Settings this makes the scanner
// halt as soon as it pops the its root tag from the stack, so no trailing
// code is consumed.
public $xml_literal = false;
private $tag_stack = array();
function __construct($src=null) {
$this->dirty_exit_recovery = array(
'DSTRING' => '/[^">]*+("|$|(?=[>]))/',
'SSTRING' => "/[^'>]*+('|$|(?=[>]))/",
'COMMENT1' => '/(?> [^\\-]+ | -(?!->))*(?:-->|$)/x',
'COMMENT2' => '/[^>]*+(?:>|$)/s',
'CDATA' => '/(?>[^\\]]+|\\](?!\\]>))*(?:\\]{2}>|$)/xs',
'ESC' => '/[^;]*+(?:;|$)/',
'TYPE' => '/[^\s]*/',
'VALUE' => '/[^\s]*/',
'HTMLTAG' => '/[^\s]*/',
);
$this->rule_tag_map = array(
'DSTRING' => 'STRING',
'SSTRING' => 'STRING',
'COMMENT1' => 'COMMENT',
'COMMENT2' => 'COMMENT',
'CDATA' => 'COMMENT',
);
parent::__construct($src);
}
function scan_child($lang) {
assert (isset($this->child_scanners[$lang]));
$scanner = $this->child_scanners[$lang];
$scanner->pos($this->pos());
$substr = $scanner->main();
$this->tokens[] = array(null, $scanner->tagged(), true);
$this->pos($scanner->pos());
if ($scanner->interrupt) {
$this->child_state = array($lang, $this->pos());
} else {
$this->child_state = null;
}
}
function init() {
$this->add_pattern('', '/&/');
if ($this->embedded_server) {
$this->add_pattern('TERM', $this->server_tags);
}
$this->add_pattern('', '/</');
$this->state_ = 'global';
if ($this->scripts) {
$js = new LuminousJavaScriptScanner($this->string());
$js->embedded_server = $this->embedded_server;
$js->embedded_html = true;
$js->server_tags = $this->server_tags;
$js->init();
$css = new LuminousCSSScanner($this->string());
$css->embedded_server = $this->embedded_server;
$css->embedded_html = true;
$css->server_tags = $this->server_tags;
$css->init();
$this->add_child_scanner('js', $js);
$this->add_child_scanner('css', $css);
}
}
private $tagname = '';
private $expecting = '';
function main() {
$this->start();
$this->interrupt = false;
while (!$this->eos()) {
$index = $this->pos();
if ($this->embedded_server && $this->check($this->server_tags)) {
$this->interrupt = true;
break;
}
if (!$this->clean_exit) {
try {
$tok = $this->resume();
if ($this->server_break($tok)) break;
$this->record($this->match(), $tok);
} catch (Exception $e) {
if (LUMINOUS_DEBUG) throw $e;
else $this->clean_exit = true;
}
continue;
}
if ($this->child_state !== null && $this->child_state[1] < $this->pos()) {
$this->scan_child($this->child_state[0]);
continue;
}
$in_tag = $this->state_ === 'tag';
if (!$in_tag) {
$next = $this->next_match(false);
if($next) {
$skip = $next[1] - $this->pos();
$this->record($this->get($skip), null);
if ($next[0] === 'TERM') {
$this->interrupt = true;
break;
}
}
} else {
$this->skip_whitespace();
if ($this->embedded_server && $this->check($this->server_tags)) {
$this->interrupt = true;
break;
}
}
$index = $this->pos();
$c = $this->peek();
$tok = null;
$get = false;
if (!$in_tag && $c === '&'
&& $this->scan('/&[^;\s]+;/')
) $tok = 'ESC';
elseif(!$in_tag && $c === '<') {
if ($this->peek(2) === '<!') {
if($this->scan('/(<)(!DOCTYPE)/i')) {
// special case: doctype
$matches = $this->match_groups();
$this->record($matches[1], null);
$this->record($matches[2], 'KEYWORD');
$this->state_ = 'tag';
continue;
}
// urgh
elseif($this->scan('/
<!\\[CDATA\\[
(?> [^\\]]+ | \\](?!\\]>) )*
(?: \\]\\]> | $ )
/ixs'
))
$tok = 'CDATA';
elseif($this->scan('/<!--(?> [^\\-]+ | (?:-(?!->))+)* (?:-->|$)/xs'))
$tok = 'COMMENT1';
elseif($this->scan('/<![^>]*+(?:>|$)/s')) $tok = 'COMMENT2';
else assert(0);
} else {
// check for <script>
$this->state_ = 'tag';
$this->expecting = 'tagname';
$get = true;
}
}
elseif($c === '>') {
$get = true;
$this->state_ = 'global';
if ($this->scripts
&& ($this->tagname === 'script' || $this->tagname === 'style'))
{
$this->record($this->get(), null);
$this->scan_child( ($this->tagname === 'script')? 'js' : 'css');
continue;
}
$this->tagname = '';
}
elseif($in_tag && $this->scan('@/\s*>@')) {
$this->state_ = 'global';
array_pop($this->tag_stack);
}
elseif($in_tag &&
$c === "'" && $this->scan("/' (?> [^'\\\\>]+ | \\\\.)* (?:'|$|(?=>))/xs")) {
$tok = 'SSTRING';
$this->expecting = '';
}
elseif($in_tag &&
$c === '"' && $this->scan('/" (?> [^"\\\\>]+ | \\\\.)* (?:"|$|(?=>))/xs')) {
$tok = 'DSTRING';
$this->expecting = '';
}
elseif($in_tag && $this->scan('@(?:(?<=<)/)?[^\s=<>/]+@') !== null) {
if ($this->expecting === 'tagname') {
$tok = 'HTMLTAG';
$this->expecting = '';
$this->tagname = strtolower($this->match());
if ($this->tagname[0] === '/') array_pop($this->tag_stack);
else $this->tag_stack[] = $this->tagname;
}
elseif($this->expecting === 'value') {
$tok = 'VALUE'; // val as in < a href=*/index.html*
$this->expecting = '';
}
else {
$tok = 'TYPE'; // attr, as in <a *HREF*= ....
}
}
elseif($in_tag && $c === '=') {
$this->expecting = 'value';
$get = true;
}
else $get = true;
if (!$get && $this->server_break($tok)) {break; }
$this->record($get? $this->get(): $this->match(), $tok);
assert ($index < $this->pos() || $this->eos());
if ($this->xml_literal && $this->state_ !== 'tag' && empty($this->tag_stack)) {
return;
}
}
}
public static function guess_language($src, $info) {
$p = 0;
// we have to be a bit careful of XML literals nested in other
// langauges here.
// We also have to becareful to take precedence over embedded CSS and JS
// but leave some room for being embedded in PHP or Rails
// so we're not going to go over 0.75
$doctype = strpos(ltrim($src), '<!DOCTYPE ');
if ($doctype === 0) return 0.75;
if (preg_match('/<(a|table|span|div)\s+class=/', $src)) $p += 0.05;
if (preg_match('%</(a|table|span|div)>%', $src)) $p += 0.05;
if (preg_match('/<(style|script)\\b/', $src)) $p += 0.15;
if (preg_match('/<!\\[CDATA\\[/', $src)) $p += 0.15;
// look for 1 tag at least every 4 lines
$lines = preg_match_all('/$/m',
preg_replace('/^\s+/m', '', $src), $m);
if (preg_match_all('%<[!?/]?[a-zA-Z_:\\-]%', $src, $m)
> $lines/4) $p += 0.15;
return $p;
}
}

View file

@ -0,0 +1,10 @@
<?php
// the identity scanner. Does what you expect.
// Implemented for consistency.
class LuminousIdentityScanner extends LuminousScanner {
public function main() {
$this->record($this->string(), null);
}
}

View file

@ -0,0 +1,301 @@
<?php
/**
* C and C++ keywords/functions
* Some Qt things here as well.
*/
global $luminous_c_funcs;
global $luminous_c_types;
global $luminous_c_keywords;
$luminous_c_keywords = array(
'asm', 'auto', 'break', 'case', 'catch', 'class', 'continue', 'const',
'const_cast', 'connect', 'default', 'delete', 'do', 'dynamic_cast',
'else', 'explicit', 'extern', 'for', 'for_each', 'friend', 'goto',
'if', 'inline', 'mutable', 'namespace', 'new', 'operator', 'private',
'protected', 'public', 'register', 'reinterpret_cast', 'return',
'static', 'static_cast', 'switch', 'sizeof', 'signed',
'template', 'this',
'throw', 'try', 'typedef', 'typeid', 'typename', 'using', 'unsigned',
'while',
'NULL', 'SIGNAL', 'SLOT', 'TRUE', 'FALSE', 'true', 'false');
$luminous_c_types = array(
'bool', 'char', 'clock_t', 'double', 'div_t', 'enum', 'float', 'fpos_t',
'int', 'int8', 'int16', 'int32', 'int64', 'int8_t', 'int16_t', 'int32_t',
'int64_t', 'long', 'ldiv_t', 'short', 'struct', 'size_t', 'ptrdiff_t',
'time_t', 'union', 'uint', 'uint8', 'uint16', 'uint32', 'uint64', 'uint8_t',
'uint16_t', 'uint32_t', 'uint64_t', 'void', 'va_list', 'wchar_t',
// C++ std stuff
'pair', 'list', 'deque', 'queue', 'priority_queue', 'set', 'stack', 'string',
'map', 'multiset', 'multimap', 'hash_set', 'hash_multiset', 'hash_map',
'hash_multimap', 'bitset', 'vector', 'valarray', 'iterator'
);
// http://en.wikipedia.org/wiki/List_of_C_functions
// inb4 deletionists
$luminous_c_funcs = array(
'assert',
'cabs',
'cacos',
'cacosh',
'carg',
'casin',
'casinh',
'catan',
'catanh',
'ccos',
'ccosh',
'cexp',
'cimag',
'cis',
'clog',
'conj',
'cpow',
'cproj',
'creal',
'csin',
'csinh',
'csqrt',
'ctan',
'ctanh',
'digittoint',
'isalnum',
'isalpha',
'isascii',
'isblank',
'iscntrl',
'isdigit',
'isgraph',
'islower',
'isprint',
'ispunct',
'isspace',
'isupper',
'isxdigit',
'toascii',
'tolower',
'toupper',
'imaxabs',
'imaxdiv',
'strtoimax',
'strtoumax',
'wcstoimax',
'wcstoumax',
'localeconv',
'setlocale',
'acos',
'asin',
'atan',
'atan2',
'atof',
'ceil',
'cos',
'cosh',
'exp',
'fabs',
'floor',
'frexp',
'ldexp',
'log',
'log10',
'modf',
'pow',
'sin',
'sinh',
'sqrt',
'tan',
'tanh',
'longjmp',
'setjmp',
'raise',
'va_arg',
'va_copy',
'va_end',
'va_start',
'offsetof',
'clearerr',
'fclose',
'feof',
'ferror',
'fflush',
'fgetc',
'fgetpos',
'fgets',
'fopen',
'freopen',
'fdopen',
'fprintf',
'fputc',
'fputs',
'fread',
'fscanf',
'fseek',
'fsetpos',
'ftell',
'fwrite',
'getc',
'getchar',
'gets',
'perror',
'printf',
'fprintf',
'sprintf',
'snprintf',
'putc',
'putchar',
'fputchar',
'puts',
'remove',
'rename',
'rewind',
'scanf',
'fscanf',
'sscanf',
'vfscanf',
'vscanf',
'vsscanf',
'setbuf',
'setvbuf',
'tmpfile',
'tmpnam',
'ungetc',
'vprintf',
'vfprintf',
'vsprintf',
'abort',
'abs',
'labs',
'atexit',
'atof',
'atoi',
'atol',
'bsearch',
'div',
'ldiv',
'exit',
'free',
'itoa',
'getenv',
'ldiv',
'ltoa',
'malloc',
'alloc',
'realloc',
'qsort',
'rand',
'srand',
'strtod',
'strtol',
'strtoul',
'system',
'memchr',
'memcmp',
'memcpy',
'memmove',
'memset',
'strcat',
'strncat',
'strchr',
'strcmp',
'strncmp',
'strcoll',
'strcpy',
'strncpy',
'strcspn',
'strerror',
'strlen',
'strpbrk',
'strrchr',
'strspn',
'strstr',
'strtok',
'strxfrm',
'asctime',
'clock',
'ctime',
'difftime',
'gmtime',
'localtime',
'mktime',
'strftime',
'time',
'btowc',
'fgetwc',
'fgetws',
'fputwc',
'fputws',
'fwide',
'fwprintf',
'fwscanf',
'getwc',
'getwchar',
'mbrlen',
'mbrtowc',
'mbsinit',
'mbsrtowcs',
'putwc',
'putwchar',
'swprintf',
'swscanf',
'ungetwc',
'vfwprintf',
'vswprintf',
'vwprintf',
'wcrtomb',
'wcscat',
'wcschr',
'wcscmp',
'wcscoll',
'wcscpy',
'wcscspn',
'wcsftime',
'wcslen',
'wcsncat',
'wcsncmp',
'wcsncpy',
'wcspbrk',
'wcsrchr',
'wcsrtombs',
'wcsspn',
'wcsstr',
'wcstod',
'wcstok',
'wcstol',
'wcstoul',
'wcsxfrm',
'wctob',
'wmemchr',
'wmemcmp',
'wmemcpy',
'wmemmove',
'wmemset',
'wprintf',
'wscanf',
'iswalnum',
'iswalpha',
'iswcntrl',
'iswctype',
'iswdigit',
'iswgraph',
'iswlower',
'iswprint',
'iswpunct',
'iswspace',
'iswupper',
'iswxdigit',
'towctrans',
'towlower',
'towupper',
'wctrans',
'wctype',
'farmalloc',
'getch',
'getche',
'gotoxy',
'getaddrinfo',
'getnameinfo',
);

View file

@ -0,0 +1,261 @@
<?php
$luminous_csharp_type_list = array(
// system
'ArgIterator',
'ArraySegment',
'Boolean',
'Byte',
'Char',
'ConsoleKeyInfo',
'DateTime',
'DateTimeOffset',
'Decimal',
'Double',
'Guid',
'Int16',
'Int32',
'Int64',
'IntPtr',
'ModuleHandle',
'Nullable',
'RuntimeArgumentHandle',
'RuntimeFieldHandle',
'RuntimeMethodHandle',
'RuntimeTypeHandle',
'SByte',
'Single',
'TimeSpan',
'TimeZoneInfo',
'TypedReference',
'UInt16',
'UInt32',
'UInt64',
'UIntPtr',
'Void',
// also system
'AccessViolationException',
'ActivationContext',
'Activator',
'AggregateException',
'AppDomain',
'AppDomainManager',
'AppDomainSetup',
'AppDomainUnloadedException',
'ApplicationException',
'ApplicationId',
'ApplicationIdentity',
'ArgumentException',
'ArgumentNullException',
'ArgumentOutOfRangeException',
'ArithmeticException',
'Array',
'ArrayTypeMismatchException',
'AssemblyLoadEventArgs',
'Attribute',
'AttributeUsageAttribute',
'BadImageFormatException',
'BitConverter',
'Buffer',
'CannotUnloadAppDomainException',
'CharEnumerator',
'CLSCompliantAttribute',
'Console',
'ConsoleCancelEventArgs',
'ContextBoundObject',
'ContextMarshalException',
'ContextStaticAttribute',
'Convert',
'DataMisalignedException',
'DBNull',
'Delegate',
'DivideByZeroException',
'DllNotFoundException',
'DuplicateWaitObjectException',
'EntryPointNotFoundException',
'Enum',
'Environment',
'EventArgs',
'Exception',
'ExecutionEngineException',
'FieldAccessException',
'FileStyleUriParser',
'FlagsAttribute',
'FormatException',
'FtpStyleUriParser',
'GC',
'GenericUriParser',
'GopherStyleUriParser',
'HttpStyleUriParser',
'IndexOutOfRangeException',
'InsufficientExecutionStackException',
'InsufficientMemoryException',
'InvalidCastException',
'InvalidOperationException',
'InvalidProgramException',
'InvalidTimeZoneException',
'Lazy',
'LdapStyleUriParser',
'LoaderOptimizationAttribute',
'LocalDataStoreSlot',
'MarshalByRefObject',
'Math',
'MemberAccessException',
'MethodAccessException',
'MissingFieldException',
'MissingMemberException',
'MissingMethodException',
'MTAThreadAttribute',
'MulticastDelegate',
'MulticastNotSupportedException',
'NetPipeStyleUriParser',
'NetTcpStyleUriParser',
'NewsStyleUriParser',
'NonSerializedAttribute',
'NotFiniteNumberException',
'NotImplementedException',
'NotSupportedException',
'Nullable',
'NullReferenceException',
'Object',
'ObjectDisposedException',
'ObsoleteAttribute',
'OperatingSystem',
'OperationCanceledException',
'OutOfMemoryException',
'OverflowException',
'ParamArrayAttribute',
'PlatformNotSupportedException',
'Random',
'RankException',
'ResolveEventArgs',
'SerializableAttribute',
'StackOverflowException',
'STAThreadAttribute',
'String',
'StringComparer',
'SystemException',
'ThreadStaticAttribute',
'TimeoutException',
'TimeZone',
'TimeZoneInfo',
'TimeZoneInfo',
'TimeZoneNotFoundException',
'Tuple',
'Type',
'TypeAccessException',
'TypeInitializationException',
'TypeLoadException',
'TypeUnloadedException',
'UnauthorizedAccessException',
'UnhandledExceptionEventArgs',
'Uri',
'UriBuilder',
'UriFormatException',
'UriParser',
'UriTemplate',
'UriTemplateEquivalenceComparer',
'UriTemplateMatch',
'UriTemplateMatchException',
'UriTemplateTable',
'UriTypeConverter',
'ValueType',
'Version',
'WeakReference',
// system.collections
'ArrayList',
'BitArray',
'CaseInsensitiveComparer',
'CaseInsensitiveHashCodeProvider',
'CollectionBase',
'Comparer',
'DictionaryBase',
'DictionaryEntry',
'Hashtable',
'ICollection',
'IComparer',
'IDictionary',
'IDictionaryEnumerator',
'IEnumerable',
'IEnumerator',
'IEqualityComparer',
'IHashCodeProvider',
'IList',
'IStructuralComparable',
'IStructuralEquatable',
'Queue',
'ReadOnlyCollectionBase',
'SortedList',
'Stack',
'StructuralComparisons',
// System.Collections.Generic
'Comparer',
'Dictionary',
'EqualityComparer',
'HashSet',
'ICollection',
'IComparer',
'IDictionary',
'IEnumerable',
'IEnumerator',
'IEqualityComparer',
'IList',
'IReadOnlyCollection',
'IReadOnlyDictionary',
'IReadOnlyList',
'ISet',
'KeyedByTypeCollection',
'KeyNotFoundException',
'KeyValuePair',
'LinkedList',
'LinkedListNode',
'List',
'Queue',
'SortedDictionary',
'SortedList',
'SortedSet',
'Stack',
'SynchronizedCollection',
'SynchronizedKeyedCollection',
'SynchronizedReadOnlyCollection',
// system.io
'BinaryReader',
'BinaryWriter',
'BufferedStream',
'Directory',
'DirectoryInfo',
'DirectoryNotFoundException',
'DriveInfo',
'DriveNotFoundException',
'EndOfStreamException',
'ErrorEventArgs',
'File',
'FileFormatException',
'FileInfo',
'FileLoadException',
'FileNotFoundException',
'FileStream',
'FileSystemEventArgs',
'FileSystemInfo',
'FileSystemWatcher',
'InternalBufferOverflowException',
'InvalidDataException',
'IODescriptionAttribute',
'IOException',
'MemoryStream',
'Path',
'PathTooLongException',
'PipeException',
'RenamedEventArgs',
'Stream',
'StreamReader',
'StreamWriter',
'StringReader',
'StringWriter',
'TextReader',
'TextWriter',
'UnmanagedMemoryAccessor',
'UnmanagedMemoryStream',
);

View file

@ -0,0 +1,6 @@
<?php
require_once (dirname(__FILE__) . '/../ecmascript.php');
require_once (dirname(__FILE__) . '/../javascript.php');
require_once (dirname(__FILE__) . '/../html.php');

View file

@ -0,0 +1,265 @@
<?php
// http://www.haskell.org/onlinereport/prelude-index.html
global $luminous_haskell_functions;
global $luminous_haskell_types;
global $luminous_haskell_values;
global $luminous_haskell_keywords;
$luminous_haskell_keywords = array('as',
'case',
'of',
'class',
'data',
'family',
'instance',
'default',
'deriving',
'do',
'forall',
'foreign',
'hiding',
'if',
'then',
'else',
'import',
'infix',
'infixl',
'infixr',
'let',
'in',
'mdo',
'module',
'newtype',
'proc',
'qualified',
'rec',
'type',
'where');
$luminous_haskell_types = array(
'Bool',
'Char',
'Double',
'Either',
'FilePath',
'Float',
'Int',
'Integer',
'IO',
'IOError',
'Maybe',
'Ordering',
'ReadS',
'ShowS',
'String',
'Bounded',
'Enum',
'Eq',
'Floating',
'Fractional',
'Functor',
'Integral',
'Monad',
'Num',
'Ord',
'Read',
'Real',
'RealFloat',
'RealFrac',
'Show'
);
$luminous_haskell_values = array(
'EQ',
'False',
'GT',
'Just',
'Left',
'LT',
'Nothing',
'Right',
'True',
);
$luminous_haskell_functions = array(
'abs',
'acos',
'acosh',
'all',
'and',
'any',
'appendFile',
'applyM',
'asTypeOf',
'asin',
'asinh',
'atan',
'atan2',
'atanh',
'break',
'catch',
'ceiling',
'compare',
'concat',
'concatMap',
'const',
'cos',
'cosh',
'curry',
'cycle',
'decodeFloat',
'div',
'divMod',
'drop',
'dropWhile',
'elem',
'encodeFloat',
'enumFrom',
'enumFromThen',
'enumFromThenTo',
'enumFromTo',
'error',
'even',
'exp',
'exponent',
'fail',
'filter',
'flip',
'floatDigits',
'floatRadix',
'floatRange',
'floor',
'fmap',
'foldl',
'foldl1',
'foldr',
'foldr1',
'fromEnum',
'fromInteger',
'fromIntegral',
'fromRational',
'fst',
'gcd',
'getChar',
'getContents',
'getLine',
'head',
'id',
'init',
'interact',
'ioError',
'isDenormalized',
'isIEEE',
'isInfinite',
'isNaN',
'isNegativeZero',
'iterate',
'last',
'lcm',
'length',
'lex',
'lines',
'log',
'logBase',
'lookup',
'map',
'mapM',
'mapM_',
'max',
'maxBound',
'maximum',
'maybe',
'min',
'minBound',
'minimum',
'mod',
'negate',
'not',
'notElem',
'null',
'odd',
'or',
'otherwise',
'pi',
'pred',
'print',
'product',
'properFraction',
'putChar',
'putStr',
'putStrLn',
'quot',
'quotRem',
'read',
'readFile',
'readIO',
'readList',
'readLn',
'readParen',
'reads',
'readsPrec',
'realToFrac',
'recip',
'rem',
'repeat',
'replicate',
'return',
'reverse',
'round',
'scaleFloat',
'scanl',
'scanl1',
'scanr',
'scanr1',
'seq',
'sequence',
'sequence_',
'show',
'showChar',
'showList',
'showParen',
'showString',
'shows',
'showsPrec',
'significand',
'signum',
'sin',
'sinh',
'snd',
'span',
'splitAt',
'sqrt',
'subtract',
'succ',
'sum',
'tail',
'take',
'takeWhile',
'tan',
'tanh',
'toEnum',
'toInteger',
'toRational',
'truncate',
'uncurry',
'undefined',
'unlines',
'until',
'unwords',
'unzip',
'unzip3',
'userError',
'words',
'writeFile',
'zip',
'zip3',
'zipWith',
'zipWith3',);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,268 @@
<?php
// TODO: These are MySQL specific
$keywords = array(
'ABORT', 'ACTION', 'ADD', 'AFTER', 'ALL', 'ALTER', 'ANALYZE', 'AS',
'ASC', 'ATTACH', 'AUTOINCREMENT', 'AUTO_INCREMENT', 'BEFORE', 'BEGIN',
'BY', 'CASCADE', 'CAST', 'CHECK', 'COLLATE', 'COLUMN',
'COMMIT', 'CONFLICT', 'CONSTRAINT', 'CREATE', 'CROSS', 'CURRENT_DATE',
'CURRENT_TIME', 'CURRENT_TIMESTAMP','DATABASE', 'DEFAULT', 'DEFERRABLE',
'DEFERRED', 'DELETE', 'DESC', 'DETACH', 'DISTINCT', 'DROP', 'EACH', 'ELSE',
'END', 'ESCAPE', 'EXCEPT', 'EXCLUSIVE', 'EXISTS', 'EXPLAIN', 'FAIL', 'FOR',
'FOREIGN', 'FROM', 'FULL', 'GLOB', 'GROUP', 'HAVING', 'IF', 'IGNORE',
'IMMEDIATE', 'IN', 'INDEX', 'INDEXED', 'INITITIALLY', 'INNER', 'INSERT',
'INSTEAD', 'INTERSECT', 'INTO', 'ISNULL', 'JOIN', 'KEY', 'LEFT', 'LIKE',
'LIMIT', 'MATCH', 'NATURAL', 'NO', 'NOTNULL', 'OF', 'OFFSET',
'ON', 'OR', 'ORDER', 'OUTER', 'PLAN', 'PRAGMA', 'PRIMARY', 'QUERY', 'RAISE',
'REFERENCES', 'REGEXP', 'REINDEX', 'RELEASE', 'RENAME', 'REPLACE', 'RESTRICT',
'RIGHT', 'ROLLBACK', 'ROW', 'SAVEPOINT', 'SELECT', 'SET', 'TABLE', 'TEMP',
'TEMPORARY', 'THEN', 'TO', 'TRANSACTION', 'TRIGGER', 'UNION', 'UNIQUE',
'UPDATE', 'USING', 'VACUUM', 'VALUES', 'VIEW', 'VIRTUAL', 'WHEN', 'WHERE',
'WITH',
// type qualifier stuff
'SIGNED', 'UNSIGNED', 'ZEROFILL',
// seem to be missing these, probably not standard
'MINVALUE', 'MAXVALUE', 'START'
);
$types = array(
'BINARY', 'BIT', 'BIGINT', 'BIGINTEGER', 'BLOB',
'CHAR', 'CLOB',
'DATE', 'DATETIME', 'DEC', 'DECIMAL', 'DOUBLE', 'DOUBLE_PRECISION',
'ENUM',
'FIXED', 'FLOAT',
'INT', 'INTEGER',
'MEDIUMINT', 'MEDIUMINTEGER',
'NUMERIC',
'REAL',
'SMALLINT', 'SMALLINTEGER',
'SET', 'TEXT', 'TIME', 'TIMESTAMP', 'TINYINT',
'TINYINTEGER',
'VARBINARY', 'VARCHAR',
'YEAR',
'ZONE' // for time zone
);
$values = array('NULL');
// http://dev.mysql.com/doc/refman/5.0/en/func-op-summary-ref.html
$operators = array('AND', 'BETWEEN', 'BINARY', 'CASE', 'DIV', 'IS',
'LIKE', 'NOT', 'SOUNDS', 'XOR');
$functions = array(
'ABS',
'ACOS',
'ADDDATE',
'ADDTIME',
'AES_DECRYPT',
'AES_ENCRYPT',
'ASCII',
'ASIN',
'ATAN2',
'ATAN',
'AVG',
'BENCHMARK',
'BIN',
'BIT_AND',
'BIT_COUNT',
'BIT_LENGTH',
'BIT_OR',
'BIT_XOR',
'CAST',
'CEIL',
'CEILING',
'CHAR_LENGTH',
'CHAR',
'CHARACTER_LENGTH',
'CHARSET',
'COALESCE',
'COERCIBILITY',
'COLLATION',
'COMPRESS',
'CONCAT_WS',
'CONCAT',
'CONNECTION_ID',
'CONV',
'CONVERT_TZ',
'Convert',
'COS',
'COT',
'COUNT',
'COUNT',
'CRC32',
'CURDATE',
'CURRENT_DATE',
'CURRENT_TIME',
'CURRENT_TIMESTAMP',
'CURRENT_USER',
'CURTIME',
'DATABASE',
'DATE_ADD',
'DATE_FORMAT',
'DATE_SUB',
'DATE',
'DATEDIFF',
'DAY',
'DAYNAME',
'DAYOFMONTH',
'DAYOFWEEK',
'DAYOFYEAR',
'DECODE',
'DEFAULT',
'DEGREES',
'DES_DECRYPT',
'DES_ENCRYPT',
'ELT',
'ENCODE',
'ENCRYPT',
'EXP',
'EXPORT_SET',
'EXTRACT',
'FIELD',
'FIND_IN_SET',
'FLOOR',
'FORMAT',
'FOUND_ROWS',
'FROM_DAYS',
'FROM_UNIXTIME',
'GET_FORMAT',
'GET_LOCK',
'GREATEST',
'GROUP_CONCAT',
'HEX',
'HOUR',
'IF',
'IFNULL',
'IN',
'INET_ATON',
'INET_NTOA',
'INSERT',
'INSTR',
'INTERVAL',
'IS_FREE_LOCK',
'IS_USED_LOCK',
'ISNULL',
'LAST_DAY',
'LAST_INSERT_ID',
'LCASE',
'LEAST',
'LEFT',
'LENGTH',
'LN',
'LOAD_FILE',
'LOCALTIME',
'LOCALTIMESTAMP,',
'LOCATE',
'LOG10',
'LOG2',
'LOG',
'LOWER',
'LPAD',
'LTRIM',
'MAKE_SET',
'MAKEDATE',
'MAKETIME',
'MASTER_POS_WAIT',
'MATCH',
'MAX',
'MD5',
'MICROSECOND',
'MID',
'MIN',
'MINUTE',
'MOD',
'MONTH',
'MONTHNAME',
'NAME_CONST',
'NOW',
'NULLIF',
'OCT',
'OCTET_LENGTH',
'OLD_PASSWORD',
'ORD',
'PASSWORD',
'PERIOD_ADD',
'PERIOD_DIFF',
'PI',
'POSITION',
'POW',
'POWER',
'ANALYSE',
'QUARTER',
'QUOTE',
'RADIANS',
'RAND',
'REGEXP',
'RELEASE_LOCK',
'REPEAT',
'REPLACE',
'REVERSE',
'RIGHT',
'RLIKE',
'ROUND',
'ROW_COUNT',
'RPAD',
'RTRIM',
'SCHEMA',
'SEC_TO_TIME',
'SECOND',
'SESSION_USER',
'SHA1',
'SIGN',
'SIN',
'SLEEP',
'SOUNDEX',
'SPACE',
'SQRT',
'STD',
'STDDEV_POP',
'STDDEV_SAMP',
'STDDEV',
'STR_TO_DATE',
'STRCMP',
'SUBDATE',
'SUBSTR',
'SUBSTRING_INDEX',
'SUBSTRING',
'SUBTIME',
'SUM',
'SYSDATE',
'SYSTEM_USER',
'TAN',
'TIME_FORMAT',
'TIME_TO_SEC',
'TIME',
'TIMEDIFF',
'TIMESTAMP',
'TIMESTAMPADD',
'TIMESTAMPDIFF',
'TO_DAYS',
'TRIM',
'TRUNCATE',
'UCASE',
'UNCOMPRESS',
'UNCOMPRESSED_LENGTH',
'UNHEX',
'UNIX_TIMESTAMP',
'UPPER',
'USER',
'UTC_DATE',
'UTC_TIME',
'UTC_TIMESTAMP',
'UUID',
'VALUES',
'VAR_POP',
'VAR_SAMP',
'VARIANCE',
'VERSION',
'WEEK',
'WEEKDAY',
'WEEKOFYEAR',
'YEAR',
'YEARWEEK');

View file

@ -0,0 +1,118 @@
<?php
$luminous_vb_values = array('False', 'Nothing', 'True');
// http://msdn.microsoft.com/en-us/library/asfcc119.aspx
$luminous_vb_operators = array('AddressOf', 'And', 'AndAlso', 'GetType',
'GetXmlNamespace', 'Is', 'IsFalse', 'IsNot', 'IsTrue', 'Mod', 'Not',
'Or', 'OrElse', 'TypeOf', 'Xor');
$luminous_vb_types = array('Boolean', 'Byte', 'CBool', 'Cbyte', 'CChar',
'CDate', 'CDbl', 'CDec', 'Char', 'CInt', 'CLng', 'CObj', 'CShort', 'CSng',
'CStr', 'CType', 'Date', 'Decimal', 'Double', 'Integer', 'Long', 'Object',
'Short', 'String');
$luminous_vb_keywords = array(
'AddHandler',
'Alias',
'Ansi',
'As',
'Assembly',
'Auto',
'ByRef',
'ByVal',
'Call',
'Case',
'Catch',
'Class',
'Const',
'Declare',
'Default',
'Delegate',
'Dim',
'DirectCast',
'Do',
'Each',
'Else',
'ElseIf',
'End',
'EndIf',
'Enum',
'Erase',
'Error',
'Event',
'Exit',
'Finally',
'For',
'Friend',
'Function',
'Get',
'GetType',
'GoSub',
'GoTo',
'Handles',
'If',
'Implements',
'Imports',
'In',
'Inherits',
'Interface',
'Let',
'Lib',
'Like',
'Loop',
'Me',
'Module',
'MustInherit',
'MustOverride',
'MyBase',
'MyClass',
'Namespace',
'New',
'Next',
'Nothing',
'NotInheritable',
'NotOverridable',
'On',
'Option',
'Optional',
'OrElse',
'Overloads',
'Overridable',
'Overrides',
'ParamArray',
'Preserve',
'Private',
'Property',
'Protected',
'Public',
'RaiseEvent',
'ReadOnly',
'ReDim',
'RemoveHandler',
'Resume',
'Return',
'Select',
'Set',
'Shadows',
'Shared',
'Single',
'Static',
'Step',
'Stop',
'Structure',
'Sub',
'SyncLock',
'Then',
'Throw',
'To',
'Try',
'Unicode',
'Until',
'Variant',
'Wend',
'When',
'While',
'With',
'WithEvents',
'WriteOnly'
);

View file

@ -0,0 +1,249 @@
<?php
global $luminous_vim_functions;
global $luminous_vim_keywords;
/*
* Credit for this list goes to `nelstrom' on github:
* https://github.com/nelstrom/SyntaxHighlighter/blob/master/scripts/shBrushVimscript.js
*/
$luminous_vim_functions = array( 'abs',
'add', 'append', 'argc', 'argidx', 'argv', 'atan', 'browse', 'browsedir',
'bufexists', 'buflisted', 'bufloaded', 'bufname', 'bufnr', 'bufwinnr',
'byte2line', 'byteidx', 'call', 'ceil', 'changenr', 'char2nr', 'cindent',
'clearmatches', 'col', 'complete', 'complete_add', 'complete_check', 'confirm',
'copy', 'cos', 'count', 'cscope_connection', 'cursor', 'deepcopy', 'delete',
'did_filetype', 'diff_filler', 'diff_hlID', 'empty', 'escape', 'eval',
'eventhandler', 'executable', 'exists', 'expand', 'expr8', 'extend', 'feedkeys',
'filereadable', 'filewritable', 'filter', 'finddir', 'findfile', 'float2nr',
'floor', 'fnameescape', 'fnamemodify', 'foldclosed', 'foldclosedend',
'foldlevel', 'foldtext', 'foldtextresult', 'foreground', 'function',
'garbagecollect', 'get', 'getbufline', 'getbufvar', 'getchar', 'getcharmod',
'getcmdline', 'getcmdpos', 'getcmdtype', 'getcwd', 'getfontname', 'getfperm',
'getfsize', 'getftime', 'getftype', 'getline', 'getloclist', 'getmatches',
'getpid', 'getpos', 'getqflist', 'getreg', 'getregtype', 'gettabwinvar',
'getwinposx', 'getwinposy', 'getwinvar', 'glob', 'globpath', 'has', 'has_key',
'haslocaldir', 'hasmapto', 'histadd', 'histdel', 'histget', 'histnr', 'hlID',
'hlexists', 'hostname', 'iconv', 'indent', 'index', 'input', 'inputdialog',
'inputlist', 'inputrestore', 'inputsave', 'inputsecret', 'insert',
'isdirectory', 'islocked', 'items', 'join', 'keys', 'len', 'libcall',
'libcallnr', 'line', 'line2byte', 'lispindent', 'localtime', 'log10', 'map',
'maparg', 'mapcheck', 'match', 'matchadd', 'matcharg', 'matchdelete',
'matchend', 'matchlist', 'matchstr', 'max', 'min', 'mkdir', 'mode',
'nextnonblank', 'nr2char', 'pathshorten', 'pow', 'prevnonblank', 'printf',
'pumvisible', 'range', 'readfile', 'reltime', 'reltimestr', 'remote_expr',
'remote_foreground', 'remote_peek', 'remote_read', 'remote_send', 'remove',
'rename', 'repeat', 'resolve', 'reverse', 'round', 'search', 'searchdecl',
'searchpair', 'searchpairpos', 'searchpos', 'server2client', 'serverlist',
'setbufvar', 'setcmdpos', 'setline', 'setloclist', 'setmatches', 'setpos',
'setqflist', 'setreg', 'settabwinvar', 'setwinvar', 'shellescape', 'simplify',
'sin', 'sort', 'soundfold', 'spellbadword', 'spellsuggest', 'split', 'sqrt',
'str2float', 'str2nr', 'strftime', 'stridx', 'string', 'strlen', 'strpart',
'strridx', 'strtrans', 'submatch', 'substitute', 'synID', 'synIDattr',
'synIDtrans', 'synstack', 'system', 'tabpagebuflist', 'tabpagenr',
'tabpagewinnr', 'tagfiles', 'taglist', 'tempname', 'tolower', 'toupper', 'tr',
'trunc', 'type', 'values', 'virtcol', 'visualmode', 'winbufnr', 'wincol',
'winheight', 'winline', 'winnr', 'winrestcmd', 'winrestview', 'winsaveview',
'winwidth', 'writefile');
$luminous_vim_keywords = array(
'Next',
'Print',
'XMLent', 'XMLns',
'abc', 'abclear', 'abo', 'aboveleft', 'acd', 'ai', 'akm', 'al', 'aleph',
'all',
'allowrevins',
'altkeymap', 'ambiwidth', 'ambw', 'anti', 'antialias', 'ar', 'arab', 'arabic',
'arabicshape', 'arabshape', 'argadd',
'arga', 'argdelete', 'argdo', 'arge', 'argedit', 'argg', 'argglobal',
'arglargs', 'arglocal', 'argument', 'ari', 'arshape', 'as', 'ascii', 'au',
'augroup', 'auto', 'autochdir', 'autocmd', 'autoindent', 'autoread',
'autowrite', 'autowriteall', 'aw', 'awa',
'bN', 'bNext', 'ba', 'background', 'backspace', 'backup', 'backupcopy',
'backupdir', 'backupext', 'backupskip', 'bad', 'badd', 'ball', 'balloondelay',
'ballooneval', 'balloonexpr', 'bd', 'bdelete', 'bdir', 'bdlay', 'bel',
'belowright', 'beval', 'bex', 'bexpr', 'bf', 'bfirst', 'bg', 'bh', 'bin',
'binary', 'biosk', 'bioskey', 'bk', 'bkc', 'bl', 'blast', 'bm', 'bmodified',
'bn', 'bnext', 'bo', 'bomb', 'botright', 'bp', 'bprevious', 'br', 'brea',
'break', 'breaka', 'breakadd', 'breakat', 'breakd', 'breakdel', 'breakl',
'breaklist', 'brewind', 'brk', 'bro', 'browse', 'browsedir', 'bs', 'bsdir',
'bsk', 'bt', 'bufdo', 'buffer', 'buffers', 'bufhidden', 'buflisted', 'buftype',
'bun', 'bunload', 'bw', 'bwipeout',
'cN', 'cNext', 'cNf', 'cNfile', 'cabc', 'cabclear', 'cad', 'caddb',
'caddbuffer', 'caddexpr', 'caddf', 'caddfile', 'cal', 'call', 'casemap', 'cat',
'catch', 'cb', 'cbuffer', 'cc', 'ccl', 'cclose', 'ccv', 'cd', 'cdpath', 'ce',
'cedit', 'center', 'cex', 'cexpr', 'cf', 'cfile', 'cfir', 'cfirst', 'cfu', 'cg',
'cgetb', 'cgetbuffer', 'cgete', 'cgetexpr', 'cgetfile', 'ch', 'change',
'changes', 'charconvert', 'chd', 'chdir', 'che', 'checkpath', 'checkt',
'checktime', 'ci', 'cin', 'cindent', 'cink', 'cinkeys', 'cino', 'cinoptions',
'cinw', 'cinwords', 'cl', 'cla', 'clast', 'clipboard', 'clist', 'clo', 'close',
'cm', 'cmap', 'cmapc', 'cmapclear', 'cmdheight', 'cmdwinheight', 'cmp', 'cms',
'cn', 'cnew', 'cnewer', 'cnext', 'cnf', 'cnfile', 'cno', 'cnoremap', 'co',
'col', 'colder', 'colo', 'colorscheme', 'columns', 'com', 'comc', 'comclear',
'command', 'comments', 'commentstring', 'comp', 'compatible', 'compiler',
'complete', 'completefunc', 'completeopt', 'con', 'conf', 'confirm', 'consk',
'conskey', 'continue', 'cope', 'copen', 'copy', 'copyindent', 'cot', 'cp',
'cpf', 'cpfile', 'cpo', 'cpoptions', 'cprevious', 'cpt', 'cq', 'cquit', 'cr',
'crewind', 'cscopepathcomp', 'cscopeprg', 'cscopequickfix', 'cscopetag',
'cscopetagorder', 'cscopeverbose', 'cspc', 'csprg', 'csqf', 'cst', 'csto',
'csverb', 'cuc', 'cul', 'cuna', 'cunabbrev', 'cursorcolumn', 'cursorline', 'cw',
'cwh', 'cwindow',
'debug', 'debugg', 'debuggreedy', 'deco', 'def', 'define', 'delc', 'delcombine',
'delcommand', 'delete', 'delf', 'delfunction', 'delm', 'delmarks', 'dex', 'dg',
'di', 'dict', 'dictionary', 'diff', 'diffexpr', 'diffg', 'diffget', 'diffoff',
'diffopt', 'diffpatch', 'diffpu', 'diffput', 'diffsplit', 'diffthis', 'diffu',
'diffupdate', 'dig', 'digraph', 'digraphs', 'dip', 'dir', 'directory',
'display', 'dj', 'djump', 'dl', 'dlist', 'do', 'doautoa', 'doautoall',
'doautocmd', 'dr', 'drop', 'ds', 'dsearch', 'dsp', 'dsplit', 'dy',
'ea', 'ead', 'eadirection', 'earlier', 'eb', 'echoe', 'echoerr', 'echohl',
'echom', 'echomsg', 'echon', 'ed', 'edcompatible', 'edit', 'ef', 'efm', 'ei',
'ek', 'el', 'else', 'elsei', 'elseif', 'em', 'emenu', 'en', 'enc', 'encoding',
'endf', 'endfo', 'endfor', 'endfunction', 'endif', 'endofline', 'endt',
'endtry', 'endw', 'endwhile', 'ene', 'enew', 'environment', 'eol', 'ep',
'equalalways', 'equalprg', 'errorbells', 'errorfile', 'errorformat', 'esckeys',
'et', 'event', 'eventignore', 'ex', 'exi', 'exit', 'expandtab', 'expression',
'exrc', 'exu', 'exusage',
'fcl', 'fcs', 'fdc', 'fde', 'fdi', 'fdl', 'fdls', 'fdm', 'fdn', 'fdo', 'fdt',
'fen', 'fenc', 'fencs', 'fex', 'ff', 'ffs', 'file', 'fileencoding',
'fileencodings', 'fileformat', 'fileformats', 'files', 'filetype', 'fillchars',
'fin', 'fina', 'finally', 'find', 'fini', 'finish', 'fir', 'first', 'fix',
'fixdel', 'fk', 'fkmap', 'flp', 'fml', 'fmr', 'fo', 'fold', 'foldc',
'foldclose', 'foldcolumn', 'foldd', 'folddoc', 'folddoclosed', 'folddoopen',
'foldenable', 'foldexpr', 'foldignore', 'foldlevel', 'foldlevelstart',
'foldmarker', 'foldmethod', 'foldminlines', 'foldnestmax', 'foldo', 'foldopen',
'foldtext', 'for', 'formatexpr', 'formatlistpat', 'formatoptions', 'formatprg',
'fp', 'fs', 'fsync', 'ft', 'fu', 'function',
'gcr', 'gd', 'gdefault', 'gfm', 'gfn', 'gfs', 'gfw', 'ghr', 'go', 'goto', 'gp',
'gr', 'grep', 'grepa', 'grepadd', 'grepformat', 'grepprg', 'gtl', 'gtt',
'guicursor', 'guifont', 'guifontset', 'guifontwide', 'guiheadroom',
'guioptions', 'guipty', 'guitablabel', 'guitabtooltip',
'ha', 'hardcopy', 'help', 'helpf', 'helpfile', 'helpfind', 'helpg', 'helpgrep',
'helpheight', 'helplang', 'helpt', 'helptags', 'hf', 'hh', 'hi', 'hid',
'hidden', 'hide', 'highlight', 'his', 'history', 'hk', 'hkmap', 'hkmapp', 'hkp',
'hl', 'hlg', 'hls', 'hlsearch',
'iabc', 'iabclear', 'ic', 'icon', 'iconstring', 'if', 'ignorecase', 'ij',
'ijump', 'il', 'ilist', 'im', 'imactivatekey', 'imak', 'imap', 'imapc',
'imapclear', 'imc', 'imcmdline', 'imd', 'imdisable', 'imi', 'iminsert', 'ims',
'imsearch', 'inc', 'include', 'includeexpr', 'incsearch', 'inde', 'indentexpr',
'indentkeys', 'indk', 'inex', 'inf', 'infercase', 'ino', 'inoremap',
'insertmode', 'is', 'isearch', 'isf', 'isfname', 'isi', 'isident', 'isk',
'iskeyword', 'isp', 'isplit', 'isprint', 'iuna', 'iunabbrev',
'join', 'joinspaces', 'js', 'ju', 'jumps',
'kee', 'keepalt', 'keepj', 'keepjumps', 'keepmarks', 'key', 'keymap',
'keymodel', 'keywordprg', 'km', 'kmp', 'kp',
'lN', 'lNext', 'lNf', 'lNfile', 'la', 'lad', 'laddb', 'laddbuffer', 'laddexpr',
'laddf', 'laddfile', 'lan', 'langmap', 'langmenu', 'language', 'last',
'laststatus', 'later', 'lazyredraw', 'lb', 'lbr', 'lbuffer', 'lc', 'lcd', 'lch',
'lchdir', 'lcl', 'lclose', 'lcs', 'le', 'left', 'lefta', 'leftabove', 'let',
'lex', 'lexpr', 'lf', 'lfile', 'lfir', 'lfirst', 'lg', 'lgetb', 'lgetbuffer',
'lgete', 'lgetexpr', 'lgetfile', 'lgr', 'lgrep', 'lgrepa', 'lgrepadd', 'lh',
'lhelpgrep', 'linebreak', 'lines', 'linespace', 'lisp', 'lispwords', 'list',
'listchars', 'll', 'lla', 'llast', 'lli', 'llist', 'lm', 'lmak', 'lmake',
'lmap', 'lmapc', 'lmapclear', 'ln', 'lne', 'lnew', 'lnewer', 'lnext', 'lnf',
'lnfile', 'lnoremap', 'lo', 'loadplugins', 'loadview', 'loc', 'lockmarks',
'lockv', 'lockvar', 'lol', 'lolder', 'lop', 'lopen', 'lp', 'lpf', 'lpfile',
'lpl', 'lprevious', 'lr', 'lrewind', 'ls', 'lsp', 'ltag', 'lv', 'lvimgrep',
'lvimgrepa', 'lvimgrepadd', 'lw', 'lwindow', 'lz',
'ma', 'maca', 'macaction', 'macatsui', 'macm', 'macmenu', 'magic', 'mak',
'make', 'makeef', 'makeprg', 'map', 'mapping', 'mark', 'marks', 'mat', 'match',
'matchpairs', 'matchtime', 'maxcombine', 'maxfuncdepth', 'maxmapdepth',
'maxmem', 'maxmempattern', 'maxmemtot', 'mco', 'mef', 'menu', 'menuitems',
'menut', 'menutranslate', 'mfd', 'mh', 'mis', 'mk', 'mkexrc', 'mks',
'mksession', 'mksp', 'mkspell', 'mkspellmem', 'mkv', 'mkvie', 'mkview',
'mkvimrc', 'ml', 'mls', 'mm', 'mmd', 'mmp', 'mmt', 'mod', 'mode', 'modeline',
'modelines', 'modifiable', 'modified', 'more', 'mouse', 'mousef', 'mousefocus',
'mousehide', 'mousem', 'mousemodel', 'mouses', 'mouseshape', 'mouset',
'mousetime', 'move', 'mp', 'mps', 'msm', 'mz', 'mzf', 'mzfile', 'mzq',
'mzquantum', 'mzscheme',
'nbkey', 'new', 'next', 'nf', 'nm', 'nmap', 'nmapc', 'nmapclear', 'nn',
'nnoremap', 'no', 'noexpandtab', 'noh', 'nohlsearch', 'noremap', 'nrformats',
'nu', 'number', 'numberwidth', 'nuw',
'odev', 'oft', 'ofu', 'om', 'omap', 'omapc', 'omapclear', 'omnifunc', 'on',
'only', 'ono', 'onoremap', 'open', 'opendevice', 'operatorfunc', 'opfunc',
'opt', 'option', 'options', 'osfiletype',
'pa', 'para', 'paragraphs', 'paste', 'pastetoggle', 'patchexpr', 'patchmode',
'path', 'pc', 'pclose', 'pdev', 'pe', 'ped', 'pedit', 'penc', 'perl', 'perld',
'perldo', 'pex', 'pexpr', 'pfn', 'ph', 'pheader', 'pi', 'pm', 'pmbcs', 'pmbfn',
'po', 'pop', 'popt', 'popu', 'popup', 'pp', 'ppop', 'pre', 'preserve',
'preserveindent', 'prev', 'previewheight', 'previewwindow', 'previous', 'print',
'printdevice', 'printencoding', 'printexpr', 'printfont', 'printheader',
'printmbcharset', 'printmbfont', 'printoptions', 'prof', 'profd', 'profdel',
'profile', 'prompt', 'promptf', 'promptfind', 'promptr', 'promptrepl', 'ps',
'psearch', 'pt', 'ptN', 'ptNext', 'pta', 'ptag', 'ptf', 'ptfirst', 'ptj',
'ptjump', 'ptl', 'ptlast', 'ptn', 'ptnext', 'ptp', 'ptprevious', 'ptr',
'ptrewind', 'pts', 'ptselect', 'pu', 'pumheight', 'put', 'pvh', 'pvw', 'pw',
'pwd', 'py', 'pyf', 'pyfile', 'python',
'qa', 'qall', 'qe', 'quit', 'quita', 'quitall', 'quoteescape',
'rdt', 'read', 'readonly', 'rec', 'recover', 'red', 'redi', 'redir', 'redo',
'redr', 'redraw', 'redraws', 'redrawstatus', 'redrawtime', 'reg', 'registers',
'remap', 'report', 'res', 'resize', 'restorescreen', 'ret', 'retab', 'retu',
'return', 'revins', 'rew', 'rewind', 'ri', 'right', 'rightb', 'rightbelow',
'rightleft', 'rightleftcmd', 'rl', 'rlc', 'ro', 'rs', 'rtp', 'ru', 'rub',
'ruby', 'rubyd', 'rubydo', 'rubyf', 'rubyfile', 'ruf', 'ruler', 'rulerformat',
'runtime', 'runtimepath', 'rv', 'rviminfo',
'sN', 'sNext', 'sa', 'sal', 'sall', 'san', 'sandbox', 'sargument', 'sav',
'saveas', 'sb', 'sbN', 'sbNext', 'sba', 'sball', 'sbf', 'sbfirst', 'sbl',
'sblast', 'sbm', 'sbmodified', 'sbn', 'sbnext', 'sbo', 'sbp', 'sbprevious',
'sbr', 'sbrewind', 'sbuffer', 'sc', 'scb', 'scr', 'scrip', 'scripte',
'scriptencoding', 'scriptnames', 'scroll', 'scrollbind', 'scrolljump',
'scrolloff', 'scrollopt', 'scs', 'se', 'sect', 'sections', 'secure', 'sel',
'selection', 'selectmode', 'sessionoptions', 'set', 'setf', 'setfiletype',
'setg', 'setglobal', 'setl', 'setlocal', 'sf', 'sfind', 'sfir', 'sfirst', 'sft',
'sh', 'shcf', 'shell', 'shellcmdflag', 'shellpipe', 'shellquote', 'shellredir',
'shellslash', 'shelltemp', 'shelltype', 'shellxquote', 'shiftround',
'shiftwidth', 'shm', 'shortmess', 'shortname', 'showbreak', 'showcmd',
'showfulltag', 'showmatch', 'showmode', 'showtabline', 'shq', 'si',
'sidescroll', 'sidescrolloff', 'sign', 'sil', 'silent', 'sim', 'simalt', 'siso',
'sj', 'sl', 'sla', 'slast', 'sleep', 'slm', 'sm', 'smagic', 'smap', 'smapc',
'smapclear', 'smartcase', 'smartindent', 'smarttab', 'smc', 'smd', 'sme',
'smenu', 'sn', 'snext', 'sni', 'sniff', 'sno', 'snomagic', 'snor', 'snoremap',
'snoreme', 'snoremenu', 'so', 'softtabstop', 'sol', 'something', 'sor', 'sort',
'source', 'sp', 'spc', 'spe', 'spell', 'spellcapcheck', 'spelld', 'spelldump',
'spellfile', 'spellgood', 'spelli', 'spellinfo', 'spelllang', 'spellr',
'spellrepall', 'spellsuggest', 'spellu', 'spellundo', 'spellw', 'spellwrong',
'spf', 'spl', 'split', 'splitbelow', 'splitright', 'spr', 'sprevious', 'sps',
'sr', 'sre', 'srewind', 'srr', 'ss', 'ssl', 'ssop', 'st', 'sta', 'stag', 'stal',
'star', 'startg', 'startgreplace', 'startinsert', 'startofline', 'startr',
'startreplace', 'statusline', 'stj', 'stjump', 'stl', 'stmp', 'stop', 'stopi',
'stopinsert', 'sts', 'stselect', 'su', 'sua', 'suffixes', 'suffixesadd', 'sun',
'sunhide', 'sunme', 'sunmenu', 'sus', 'suspend', 'sv', 'sview', 'sw',
'swapfile', 'swapsync', 'swb', 'swf', 'switchbuf', 'sws', 'sxq', 'syn',
'syncbind', 'synmaxcol', 'syntax',
'tN', 'tNext', 'ta', 'tab', 'tabN', 'tabNext', 'tabc', 'tabclose', 'tabd',
'tabdo', 'tabe', 'tabedit', 'tabf', 'tabfind', 'tabfir', 'tabfirst', 'tabl',
'tablast', 'tabline', 'tabm', 'tabmove', 'tabn', 'tabnew', 'tabnext', 'tabo',
'tabonly', 'tabp', 'tabpagemax', 'tabprevious', 'tabr', 'tabrewind', 'tabs',
'tabstop', 'tag', 'tag_listfiles', 'tagbsearch', 'taglength', 'tagrelative',
'tags', 'tagstack', 'tal', 'tb', 'tbi', 'tbidi', 'tbis', 'tbs', 'tc', 'tcl',
'tcld', 'tcldo', 'tclf', 'tclfile', 'te', 'tearoff', 'tenc', 'term', 'termbidi',
'termencoding', 'terse', 'textauto', 'textmode', 'textwidth', 'tf', 'tfirst',
'tgst', 'th', 'thesaurus', 'throw', 'tildeop', 'timeout', 'timeoutlen', 'title',
'titlelen', 'titleold', 'titlestring', 'tj', 'tjump', 'tl', 'tlast', 'tm',
'tmenu', 'tn', 'tnext', 'to', 'toolbar', 'toolbariconsize', 'top', 'topleft',
'tp', 'tpm', 'tprevious', 'tr', 'trewind', 'try', 'ts', 'tselect', 'tsl', 'tsr',
'ttimeout', 'ttimeoutlen', 'ttm', 'tty', 'ttybuiltin', 'ttyfast', 'ttym',
'ttymouse', 'ttyscroll', 'ttytype', 'tu', 'tunmenu', 'tw', 'tx',
'uc', 'ul', 'una', 'unabbreviate', 'undo', 'undoj', 'undojoin', 'undol',
'undolevels', 'undolist', 'unh', 'unhide', 'unl', 'unlet', 'unlo', 'unlockvar',
'up', 'update', 'updatecount', 'updatetime', 'ut',
'var', 'vb', 'vbs', 'vdir', 've', 'verb', 'verbose', 'verbosefile', 'version',
'vert', 'vertical', 'vfile', 'vi', 'vie', 'view', 'viewdir', 'viewoptions',
'vim', 'vimgrep', 'vimgrepa', 'vimgrepadd', 'viminfo', 'virtualedit', 'visual',
'visualbell', 'viu', 'viusage', 'vm', 'vmap', 'vmapc', 'vmapclear', 'vn', 'vne',
'vnew', 'vnoremap', 'vop', 'vs', 'vsplit',
'wN', 'wNext', 'wa', 'wak', 'wall', 'warn', 'wb', 'wc', 'wcm', 'wd',
'weirdinvert', 'wfh', 'wfw', 'wh', 'whichwrap', 'while', 'wi', 'wig',
'wildchar', 'wildcharm', 'wildignore', 'wildmenu', 'wildmode', 'wildoptions',
'wim', 'win', 'winaltkeys', 'winc', 'wincmd', 'windo', 'window', 'winfixheight',
'winfixwidth', 'winheight', 'winminheight', 'winminwidth', 'winp', 'winpos',
'winsize', 'winwidth', 'wiv', 'wiw', 'wm', 'wmh', 'wmnu', 'wmw', 'wn', 'wnext',
'wop', 'wp', 'wprevious', 'wq', 'wqa', 'wqall', 'wrap', 'wrapmargin',
'wrapscan', 'write', 'writeany', 'writebackup', 'writedelay', 'ws', 'wsverb',
'wv', 'wviminfo', 'ww',
'xa', 'xall', 'xit', 'xm', 'xmap', 'xmapc', 'xmapclear', 'xmenu',
'xn', 'xnremap', 'xnoreme', 'xnoremenu', 'xunme', 'xunmenu',
'yank'
);

View file

@ -0,0 +1,34 @@
<?php
require_once(dirname(__FILE__) . '/include/java_func_list.php');
class LuminousJavaScanner extends LuminousSimpleScanner {
function init() {
$this->add_identifier_mapping('KEYWORD',
$GLOBALS['luminous_java_keywords']);
$this->add_identifier_mapping('TYPE', $GLOBALS['luminous_java_types']);
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_ML);
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_SL);
$this->add_pattern('STRING', LuminousTokenPresets::$DOUBLE_STR);
$this->add_pattern('CHARACTER', LuminousTokenPresets::$SINGLE_STR);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_HEX);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_REAL);
$this->add_pattern('IDENT', '/[a-zA-Z$_][$\w]*/');
$this->add_pattern('OPERATOR', '/[!%^&*\-=+:?|<>]+/');
// this is called an annotation
// http://download.oracle.com/javase/1,5.0/docs/guide/language/annotations.html
$this->add_pattern('FUNCTION', '/@[\w]+/');
}
public static function guess_language($src, $info) {
$p = 0;
if (preg_match('/^import\s+java\./m', $src)) return 1.0;
if (preg_match('/System\.out\.print/', $src)) $p += 0.2;
if (preg_match('/public\s+static\s+void\s+main\\b/', $src)) $p += 0.2;
return $p;
}
}

View file

@ -0,0 +1,26 @@
<?php
class LuminousJavaScriptScanner extends LuminousECMAScriptScanner {
// mostly the same for now
public static function guess_language($src, $info) {
// JavaScript is surprisingly indistinct when you think about it,
// so these are a bit of a stretch and it's hard to ever return a
// value more than ~40%
$p = 0;
// var x =
// not amazingly distinct, but something
if (preg_match('/var\s++\w++\s*+=/', $src)) $p += 0.05;
// $, jquery
if (preg_match('/\\b$\\( | jQuery/x', $src)) $p += 0.20;
// typeof x == undefined
if (preg_match('/typeof\s++\w++\s*+[!=]{2,3}+\s*+[\'"]?+undefined/i', $src))
$p += 0.10;
if (strpos($src, 'document.') !== false) $p += 0.10;
if (strpos($src, 'Math.') !== false) $p += 0.05;
// Anonymous functions
if (preg_match('/function\s*+\\([^)]*+\\)\s*+\\{/', $src))
$p += 0.05;
return $p;
}
}

View file

@ -0,0 +1,106 @@
<?php
class LuminousJSONScanner extends LuminousScanner {
private $stack = array();
public function init() {
$this->add_identifier_mapping('KEYWORD', array('true', 'false', 'null'));
}
public function state() {
if (!empty($this->stack)) return $this->stack[count($this->stack)-1][0];
else return null;
}
private function expecting($x=null) {
if ($x !== null) {
if (!empty($this->stack)) $this->stack[count($this->stack)-1][1] = $x;
}
if (!empty($this->stack)) return $this->stack[count($this->stack)-1][1];
else return null;
}
function main() {
while (!$this->eos()) {
$tok = null;
$c = $this->peek();
list($state, $expecting) = array($this->state(), $this->expecting());
$this->skip_whitespace();
if ($this->eos()) break;
if ($this->scan(LuminousTokenPresets::$NUM_REAL) !== null) {
$tok = 'NUMERIC';
}
elseif($this->scan('/[a-zA-Z]\w*/')) {
$tok = 'IDENT';
}
elseif($this->scan(LuminousTokenPresets::$DOUBLE_STR)) {
$tok = ($state === 'obj' && $expecting === 'key')? 'TYPE' : 'STRING';
}
elseif($this->scan('/\[/')) {
$this->stack[] = array('array', null);
$tok = 'OPERATOR';
}
elseif($this->scan('/\]/')) {
if ($state === 'array') {
array_pop($this->stack);
$tok = 'OPERATOR';
}
}
elseif($this->scan('/\{/')) {
$this->stack[] = array('obj', 'key');
$tok = 'OPERATOR';
}
elseif($state === 'obj' && $this->scan('/\}/')) {
array_pop($this->stack);
$tok = 'OPERATOR';
}
elseif($state === 'obj' && $this->scan('/:/')) {
$this->expecting('value');
$tok = 'OPERATOR';
}
elseif($this->scan('/,/')) {
if ($state === 'obj') {
$this->expecting('key');
$tok = 'OPERATOR';
}
elseif($state === 'array') $tok = 'OPERATOR';
}
else $this->scan('/./');
$this->record($this->match(), $tok);
}
}
public static function guess_language($src, $info) {
// JSON is fairly hard to guess
$p = 0;
$src_ = trim($src);
if (!empty($src_)) {
$char = $src_[0];
$char2 = $src_[strlen($src_)-1];
$str = '"(?>[^"\\\\]+|\\\\.)"';
// looks like an object or array
if ( ($char === '[' && $char2 === ']')
|| ($char === '{' && $char2 === '}'))
{
$p += 0.05;
}
elseif(preg_match("/^(?:$str|(\d+(\.\d+)?([eE]\d+)?)|true|false|null)$/",
$src_))
{
// just a string or number or value
$p += 0.1;
}
}
return $p;
}
}

View file

@ -0,0 +1,68 @@
<?php
/*
* LaTeX scanner,
* brief explanation: we're using the stateful scanner to handle marginally
* different rulesets in math blocks.
* We could add in an awful lot of detail, everything is pretty generic right
* now, we don't look for any specific names or anything, but it'll suffice
* for basic highlighting.
*/
class LuminousLatexScanner extends LuminousStatefulScanner {
function init() {
// math states
$this->add_pattern('displaymath', '/\\$\\$/', '/\\$\\$/');
// literal '\[' and '\]'
$this->add_pattern('displaymath', '/\\\\\\[/', '/\\\\\\]/');
$this->add_pattern('mathmode', '/\\$/', '/\\$/');
// terminals
$this->add_pattern('COMMENT', '/%.*/');
$this->add_pattern('NUMERIC', '/\d+(\.\d+)?\w*/');
$this->add_pattern('MATH_FUNCTION', '/\\\\(?:[a-z_]\w*|[^\]])/i');
$this->add_pattern('MATHOP', '/[\\*^\\-=+]+/');
$this->add_pattern('FUNCTION', '/\\\\(?:[a-z_]\w*|.)/i');
$this->add_pattern('IDENT', '/[a-z_]\w*/i');
$this->add_pattern('OPERATOR', '/[\[\]\{\}]+/');
$math_transition = array('NUMERIC', 'MATH_FUNCTION', 'MATHOP');
$this->transitions = array(
'initial' => array('COMMENT', 'OPERATOR', 'displaymath', 'mathmode',
'FUNCTION', 'IDENT'),
// omitting initial state defn. makes it transition to everything
'displaymath' => $math_transition,
'mathmode' => $math_transition,
);
$this->rule_tag_map = array(
'displaymath' => 'INTERPOLATION',
'mathmode' => 'INTERPOLATION',
'MATHOP' => 'OPERATOR',
'MATH_FUNCTION' => 'VALUE', // arbitrary way to distinguish it from non
// math mode functions
);
}
public static function guess_language($src, $info) {
$p = 0.0;
foreach(array('documentclass', 'usepackage', 'title',
'maketitle', 'end') as $cmd)
{
if (strpos($src, '\\' . $cmd) !== false) $p += 0.1;
}
// count the number of backslashes
$bslashes = substr_count($src, '\\');
if ($bslashes > $info['num_lines']) {
$p += 0.1;
}
if (substr_count($src, '%') > $info['num_lines']/10) {
$p += 0.02;
}
return $p;
}
}

View file

@ -0,0 +1,83 @@
<?PHP
/*
* HAI
* I HAS PERSONAL INTEREST IN LOLCODE THATS WHY ITS HERE KTHX.
* BTW PHP IS MOSTLY CASE INSENSITIVE
*/
CLASS LUMINOUSLOLCODESCANNER EXTENDS LUMINOUSSIMPLESCANNER {
FUNCTION FUNCDEF_OVERRIDE($MATCHES) {
$this->RECORD($MATCHES[0], 'KEYWORD');
$this->POS_SHIFT(STRLEN($MATCHES[0]));
$this->skip_whitespace();
IF ($this->SCAN('/[a-z_]\w*/i')) {
$this->RECORD($this->MATCH(), 'USER_FUNCTION');
$this->user_defs[$this->MATCH()] = 'FUNCTION';
}
}
FUNCTION STR_FILTER($TOKEN) {
$TOKEN = LUMINOUSUTILS::ESCAPE_TOKEN($TOKEN);
$STR = &$TOKEN[1];
$STR = PREG_REPLACE('/:
(?:
(?:[\)o":]|&gt;)
|\([a-fA-F0-9]*\)
|\[[A-Z ]*\]
|\{\w*\}
)/x', '<VARIABLE>$0</VARIABLE>', $STR);
RETURN $TOKEN;
}
FUNCTION INIT() {
$this->ADD_FILTER('STRING', array($this, 'STR_FILTER'));
$this->REMOVE_FILTER('constant');
$this->ADD_PATTERN('COMMENT', '/(?s:OBTW.*?TLDR)|BTW.*/');
$this->ADD_PATTERN('STRING', '/" (?> [^":]+ | :.)* "/x');
$this->ADD_PATTERN('STRING', "/' (?> [^':]+ | :.)* '/x");
$this->ADD_PATTERN('OPERATOR',
'/
\\b
(?:
(?:ALL|ANY|BIGGR|BOTH|DIFF|EITHER|PRODUKT|QUOSHUNT
|MOD|SMALLR|SUM|WON)\s+OF\\b
|
BOTH\s+SAEM\\b
|
(?:BIGGR|SMALLR)\s+THAN\\b
|
(?:AN|NOT)\\b
)
/x');
$this->ADD_PATTERN('FUNC_DEF', '/how\s+duz\s+i\\b/i');
$this->overrides['FUNC_DEF'] = array($this, 'FUNCDEF_OVERRIDE');
$this->ADD_PATTERN('NUMERIC', LUMINOUSTOKENPRESETS::$NUM_REAL);
$this->ADD_PATTERN('IDENT', '/[a-zA-Z_]\w*\\??/');
$this->ADD_IDENTIFIER_MAPPING('VALUE', array('FAIL', 'WIN'));
$this->ADD_IDENTIFIER_MAPPING('TYPE', array('NOOB', 'NUMBAR', 'NUMBR',
'TROOF', 'YARN'));
$this->ADD_IDENTIFIER_MAPPING('KEYWORD', array('A', 'CAN',
'DUZ', 'HAI',
'KTHX', 'KTHXBYE', 'HAS', 'HOW', 'I', 'IM', 'IN', 'IS', 'IZ',
'ITS', 'ITZ',
'IF', 'FOUND', 'GTFO', 'MAEK', 'MEBBE', 'NO', 'NOW', 'O', 'OIC',
'OMG', 'OMGWTF', 'RLY', 'RLY?', 'R', 'SAY', 'SO', 'TIL', 'YA', 'YR', 'U',
'WAI', 'WILE', 'WTF?'));
$this->ADD_IDENTIFIER_MAPPING('FUNCTION', array('GIMMEH', 'VISIBLE',
'UPPIN', 'NERFIN'));
}
public static function guess_language($src, $info) {
$p = 0.0;
foreach(array('OMGWTF', 'I CAN HAS', 'GTFO', 'HOW DUZ I', 'IM IN YR',
'IM IN UR', 'I HAS A', 'I HAZ A', ' UPPIN', 'NERFIN', 'TROOF', 'NUMBAR',
'NUMBR') as $str)
{
if (strpos($src, " $str ") !== false) $p += 0.1;
}
return $p;
}
}

View file

@ -0,0 +1,45 @@
<?php
/*
* Matlab's pretty simple. Hoorah
*/
class LuminousMATLABScanner extends LuminousSimpleScanner {
// Comments can nest. This beats a PCRE recursive regex, because they are
// pretty flimsy and crash/stack overflow easily
function comment_override($matches) {
$this->nestable_token('COMMENT', '/%\\{/', '/%\\}/');
}
function init() {
// these can nest so we override this
$this->add_pattern('COMMENT_ML', '/%\\{/');
$this->add_pattern('COMMENT', '/%.*/');
$this->add_pattern('IDENT', '/[a-z_]\w*/i');
// stray single quotes are a unary operator when they're attached to
// an identifier or return value, or something. so we're going to
// use a lookbehind to exclude those
$this->add_pattern('STRING',
"/(?<![\w\)\]\}']) ' (?: [^']+ | '')* ($|')/x");
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_HEX);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_REAL);
$this->add_pattern('OPERATOR', "@[¬!%^&*\-+=~;:|<>,./?]+|'@");
$this->overrides = array('COMMENT_ML' => array($this, 'comment_override'));
include(dirname(__FILE__) . '/include/matlab_func_list.php');
$this->add_identifier_mapping('KEYWORD', $luminous_matlab_keywords);
$this->add_identifier_mapping('VALUE', $luminous_matlab_values);
$this->add_identifier_mapping('FUNCTION', $luminous_matlab_functions);
}
public static function guess_language($src, $info) {
$p = 0;
// matlab comments are quite distinctive
if (preg_match('/%\\{.*%\\}/s', $src)) $p += 0.25;
return $p;
}
}

View file

@ -0,0 +1,525 @@
<?php
/*
* Like ruby, I think it's impossible to fully tokenize Perl without
* executing some of the code to disambiguate some symbols. As such, we're
* going to settle for 'probably right' rather than 'definitely right'.
*
* TODO: I think this is mostly complete but it needs interpolation
* highlighting in strings and heredoc, and a regex highlighting filter,
* probably a stream filter
*/
class LuminousPerlScanner extends LuminousSimpleScanner {
// keeps track of heredocs we need to handle
private $heredoc = null;
// helper function:
// consumes a string until the given delimiter (which may be balanced).
// will handle nested balanced delimiters.
// this is used as the general case for perl quote-operators like:
// q/somestring/ q"somestring", q@somestring@, q[some[]string]
// it can be called twice for s/someregex/somereplacement/
// expects the initial opening delim to already have been consumed
function consume_string($delimiter, $type) {
$close = LuminousUtils::balance_delimiter($delimiter);
$balanced = $close !== $delimiter;
$patterns = array( '/(?<!\\\\)((?:\\\\\\\\)*)('
. preg_quote($close, '/') . ')/');
if ($balanced) {
$patterns[] = '/(?<!\\\\)((?:\\\\\\\\)*)('
. preg_quote($delimiter, '/') . ')/';
}
$stack = 1; // we're already inside the string
$start = $this->pos();
$close_delimiter_match = null;
while($stack) {
$next = $this->get_next($patterns);
if ($next[0] === -1) {
$this->terminate();
$finish = $this->pos();
break;
}
elseif($balanced && $next[1][2] === $delimiter) {
$stack++;
$finish = $next[0] + strlen($next[1][0]);
}
elseif($next[1][2] === $close) {
$stack--;
if (!$stack)
$close_delimiter_match = $next[1][2];
$finish = $next[0] + strlen($next[1][1]);
}
else assert(0);
$this->pos($next[0] + strlen($next[1][0]));
}
$substr = substr($this->string(), $start, $finish-$start);
// special case for qw, the string is not a 'STRING', it is actually
// a whitespace separated list of strings. So we need to split it and
// record them separately
if ($type === 'SPLIT_STRING') {
foreach(preg_split('/(\s+)/',
$substr, -1, PREG_SPLIT_DELIM_CAPTURE) as $token) {
if (preg_match('/^\s/', $token)) {
$this->record($token, null);
} else {
$this->record($token, 'STRING');
}
}
} else {
$this->record($substr, $type);
}
if ($close_delimiter_match !== null) {
$this->record($close_delimiter_match, 'DELIMITER');
}
}
// Helper function: guesses whether or not a slash is a regex delimiter
// by looking behind in the token stream.
function is_delimiter() {
for($i = count($this->tokens) - 1; $i >= 0; $i--) {
$t = $this->tokens[$i];
if ($t[0] === null || $t[0] === 'COMMENT') continue;
elseif ($t[0] === 'OPENER' || $t[0] === 'OPERATOR') return true;
elseif ($t[0] === 'IDENT') {
switch($t[1]) {
// named operators
case 'lt':
case 'gt':
case 'le':
case 'ge':
case 'eq':
case 'ne':
case 'cmp':
case 'and':
case 'or':
case 'xor':
// other keywords/functions
case 'if':
case 'elsif':
case 'while':
case 'unless':
case 'split':
case 'print':
return true;
}
}
return false;
}
return true;
}
// override function for slashes, to disambiguate regexen from division
// operators.
function slash_override($matches) {
$this->pos( $this->pos() + strlen($matches[0]) );
// this can catch '//', which I THINK is an operator but I could be wrong.
if (strlen($matches[0]) === 2 || !$this->is_delimiter()) {
$this->record($matches[0], 'OPERATOR');
} else {
$this->record($matches[0], 'DELIMITER');
$this->consume_string($matches[0], 'REGEX');
if ($this->scan('/[cgimosx]+/')) {
$this->record($this->match(), 'KEYWORD');
}
}
}
// override function for 'quote-like operators'
// e.g. m"hello", m'hello', m/hello/, m(hello), m(he()l()o())
function str_override($matches) {
$this->pos( $this->pos() + strlen($matches[0]) );
$this->record($matches[0], 'DELIMITER');
$f = $matches[1];
$type = 'STRING';
if ($f === 'm' || $f === 'qr' || $f === 's' || $f === 'tr'
|| $f === 'y') $type = 'REGEX';
elseif($f === 'qw') $type = 'SPLIT_STRING';
$this->consume_string($matches[3], $type);
if ($f === 's' || $f === 'tr' || $f === 'y') {
// s/tr/y take two strings, e.g. s/something/somethingelse/, so we
// have to consume the next delimiter (if it exists) and consume the
// string, again.
// if delims were balanced, there's a new delimiter right here, e.g.
// s[something][somethingelse]
$this->skip_whitespace();
$balanced = LuminousUtils::balance_delimiter($matches[3]) !== $matches[3];
if ($balanced) {
$delim2 = $this->scan('/[^a-zA-Z0-9]/');
if ($delim2 !== null) {
$this->record($delim2, 'DELIMITER');
$this->consume_string($delim2, 'STRING');
}
}
// if they weren't balanced then the delimiter is the same, and has
// already been consumed as the end-delim to the first pattern
else {
$this->consume_string($matches[3], 'STRING');
}
}
if ($type === 'REGEX' && $this->scan('/[cgimosxpe]+/')) {
$this->record($this->match(), 'KEYWORD');
}
}
// this override handles the heredoc declaration, and makes a note of it
// it adds a new token (a newline) which is overridden to invoke the real
// heredoc handling. This is because in Perl, heredocs declarations need not
// be the end of the line so we can't necessarily start heredocing straight
// away.
function heredoc_override($matches) {
list($group, $op, $quote1, $delim, $quote2) = $matches;
$this->record($op, 'OPERATOR');
// Now, if $quote1 is '\', then $quote2 is empty. If quote2 is empty
// but quote1 is not '\', this is not a heredoc.
if ($quote1 === '\\' && $quote2 === '') {
$this->record($quote1 . $delim, 'DELIMITER');
} elseif($quote2 === '' && $quote1 !== '') {
// this is the error case
// shift to the end of the op and break
$this->pos_shift(strlen($op));
return;
} else {
$this->record($quote1 . $delim . $quote2, 'DELIMITER');
}
$this->pos_shift(strlen($group));
// TODO. the quotes (matches[2] and matches[4]) are ignored for now, but
// they mean something w.r.t interpolation.
$this->heredoc = $delim;
$this->add_pattern('HEREDOC_NL', "/\n/");
$this->overrides['HEREDOC_NL'] = array($this, 'heredoc_real_override');
}
// this override handles the actual heredoc text
function heredoc_real_override($matches) {
$this->record($matches[0], null);
$this->pos_shift(strlen($matches[0]));
// don't need this anymore
$this->remove_pattern('HEREDOC_NL');
assert($this->heredoc !== null);
$delim = preg_quote($this->heredoc);
$substr = $this->scan_until('/^' . $delim . '\\b/m');
if ($substr !== null) {
$this->record($substr, 'HEREDOC');
$delim_ = $this->scan('/' . $delim . '/');
assert($delim !== null);
$this->record($delim_, 'DELIMITER');
} else {
$this->record($this->rest(), 'HEREDOC');
$this->terminate();
}
}
// halts highlighting on __DATA__ and __END__
function term_override($matches) {
$this->record($matches[0], 'DELIMITER');
$this->pos( $this->pos() + strlen($matches[0]) );
$this->record($this->rest(), null);
$this->terminate();
}
// pod cuts might be very long and trigger the backtrack limit, so
// we do it the old fashioned way
function pod_cut_override($matches) {
$line = $this->scan('/^=.*/m');
assert($line !== null);
$term = '/^=cut$|\\z/m';
$substr = $this->scan_until($term);
assert($substr !== null);
$end = $this->scan($term);
assert($end !== null);
$this->record($line . $substr . $end, 'DOCCOMMENT');
}
function init() {
$this->add_pattern('COMMENT', '/#.*/');
// pod/cut documentation
$this->add_pattern('podcut', '/^=[a-zA-Z_]/m');
$this->overrides['podcut'] = array($this, 'pod_cut_override');
// variables
$this->add_pattern('VARIABLE', '/[\\$%@][a-z_]\w*/i');
// special variables http://www.kichwa.com/quik_ref/spec_variables.html
$this->add_pattern('VARIABLE', '/\\$[\|%=\-~^\d&`\'+_\.\/\\\\,"#\\$\\?\\*O\\[\\];!@]/');
// `backticks` (shell cmd)
$this->add_pattern('CMD', '/`(?: [^`\\\\]++ | \\\\ . )*+ (?:`|$)/x');
// straight strings
$this->add_pattern('STRING', LuminousTokenPresets::$DOUBLE_STR);
$this->add_pattern('STRING', LuminousTokenPresets::$SINGLE_STR);
// terminators
$this->add_pattern('TERM', '/__(?:DATA|END)__/');
// heredoc (overriden)
$this->add_pattern('HEREDOC', '/(<<)([\'"`\\\\]?)([a-zA-Z_]\w*)(\\2?)/');
// operators, slash is a special case and is overridden
$this->add_pattern('OPERATOR', '/[!%^&*\-=+;:|,\\.?<>~\\\\]+/');
$this->add_pattern('SLASH', '%//?%');
// we care about 'openers' for regex-vs-division disambiguatation
$this->add_pattern('OPENER', '%[\[\{\(]+%x');
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_HEX);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_REAL);
// quote-like operators. we override these.
// I got these out of the old luminous tree, I don't know how accurate
// or complete they are.
// According to psh, delimiters can be escaped?
$this->add_pattern('DELIMETERS',
'/(q[rqxw]?|m|s|tr|y)([\s]*)(\\\\?[^a-zA-Z0-9\s])/');
$this->add_pattern('IDENT', '/[a-zA-Z_]\w*/');
$this->overrides['DELIMETERS'] = array($this, 'str_override');
$this->overrides['SLASH'] = array($this, 'slash_override');
$this->overrides['HEREDOC'] = array($this, 'heredoc_override');
$this->overrides['TERM'] = array($this, 'term_override');
// map cmd to a 'function' and get rid of openers
$this->rule_tag_map = array(
'CMD' => 'FUNCTION',
'OPENER' => null,
);
// this sort of borks with the strange regex delimiters
$this->remove_filter('pcre');
/************************************************************************/
// data definition follows.
// https://www.physiol.ox.ac.uk/Computing/Online_Documentation/Perl-5.8.6/index-functions-by-cat.html
$this->add_identifier_mapping('KEYWORD', array( 'bless',
'caller', 'continue', 'dbmclose', 'dbmopen',
'defined',
'delete', 'die', 'do', 'dump', 'else', 'elsif',
'eval', 'exit', 'for', 'foreach', 'goto', 'import', 'if', 'last', 'local',
'my',
'next', 'no',
'our', 'package', 'prototype', 'redo', 'ref', 'reset',
'return', 'require', 'scalar', 'sub', 'tie', 'tied',
'undef',
'utie',
'unless', 'use', 'wantarray', 'while'));
$this->add_identifier_mapping('OPERATOR', array('lt', 'gt', 'le',
'ge', 'eq', 'ne', 'cmp', 'and', 'or', 'xor'));
$this->add_identifier_mapping('FUNCTION', array(
'chomp',
'chop',
'chr',
'crypt',
'hex',
'index',
'lc',
'lcfirst',
'length',
'oct',
'ord',
'pack',
'reverse',
'rindex',
'sprintf',
'substr',
'uc',
'ucfirst',
'pos',
'quotemeta',
'split',
'study',
'abs',
'atan2',
'cos',
'exp',
'hex',
'int',
'log',
'oct',
'rand',
'sin',
'sqrt',
'srand',
'pop',
'push',
'shift',
'splice',
'unshift',
'grep',
'join',
'map',
'reverse',
'sort',
'unpack',
'delete',
'each',
'exists',
'keys',
'values',
'binmode',
'close',
'closedir',
'dbmclose',
'dbmopen',
'die',
'eof',
'fileno',
'flock',
'format',
'getc',
'print',
'printf',
'read',
'readdir',
'readline',
'rewinddir',
'seek',
'seekdir',
'select',
'syscall',
'sysread',
'sysseek',
'syswrite',
'tell',
'telldir',
'truncate',
'warn',
'write',
'pack',
'read',
'syscall',
'sysread',
'sysseek',
'syswrite',
'unpack',
'vec',
'chdir',
'chmod',
'chown',
'chroot',
'fcntl',
'glob',
'ioctl',
'link',
'lstat',
'mkdir',
'open',
'opendir',
'readlink',
'rename',
'rmdir',
'stat',
'symlink',
'sysopen',
'umask',
'unlink',
'utime',
'alarm',
'exec',
'fork',
'getpgrp',
'getppid',
'getpriority',
'kill',
'pipe',
'qx/STRING/',
'readpipe',
'setpgrp',
'setpriority',
'sleep',
'system',
'times',
'wait',
'waitpid',
'accept',
'bind',
'connect',
'getpeername',
'getsockname',
'getsockopt',
'listen',
'recv',
'send',
'setsockopt',
'shutdown',
'socket',
'socketpair',
'msgctl',
'msgget',
'msgrcv',
'msgsnd',
'semctl',
'semget',
'semop',
'shmctl',
'shmget',
'shmread',
'shmwrite',
'endgrent',
'endhostent',
'endnetent',
'endpwent',
'getgrent',
'getgrgid',
'getgrnam',
'getlogin',
'getpwent',
'getpwnam',
'getpwuid',
'setgrent',
'setpwent',
'endprotoent',
'endservent',
'gethostbyaddr',
'gethostbyname',
'gethostent',
'getnetbyaddr',
'getnetbyname',
'getnetent',
'getprotobyname',
'getprotobynumber',
'getprotoent',
'getservbyname',
'getservbyport',
'getservent',
'sethostent',
'setnetent',
'setprotoent',
'setservent',
'gmtime',
'localtime',
'time',
'times'));
}
public static function guess_language($src, $info) {
// check the shebang
if (preg_match('/^#!.*\\bperl\\b/', $src)) return 1.0;
$p = 0;
if (preg_match('/\\$[a-zA-Z_]+/', $src)) $p += 0.02;
if (preg_match('/@[a-zA-Z_]+/', $src)) $p += 0.02;
if (preg_match('/%[a-zA-Z_]+/', $src)) $p += 0.02;
if (preg_match('/\\bsub\s+\w+\s*\\{/', $src)) $p += 0.1;
if (preg_match('/\\bmy\s+[$@%]/', $src)) $p += 0.05;
// $x =~ s/
if (preg_match('/\\$[a-zA-Z_]\w*\s+=~\s+s\W/', $src)) $p += 0.15;
return $p;
}
}

View file

@ -0,0 +1,251 @@
<?php
require_once( dirname(__FILE__) . '/include/php_func_list.php');
/*
* This is not a scanner called by an external interface, it's controlled
* by LuminousPHPScanner (defined in this file).
*
* It should break when it sees a '?>', but it should assume it's in php
* when it's called.
*/
class LuminousPHPSubScanner extends LuminousScanner {
protected $case_sensitive = false;
public $snippet = false;
function init() {
$this->add_pattern('TERM', '/\\?>/');
$this->add_pattern('COMMENT', '% (?://|\#) .*? (?=\\?>|$) %xm');
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_ML);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_HEX);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_REAL);
// this should be picked up by the LuminousPHPScanner, but in case
// a user incorrectly calls the PHP-snippet scanner, we detect it.
$this->add_pattern('DELIMITER', '/<\?(?:php)?/');
$this->add_pattern('OPERATOR', '@[!%^&*\\-=+~:<>/\\|\\.;,]+|\\?(?!>)@');
$this->add_pattern('VARIABLE', '/\\$\\$?[a-zA-Z_]\w*/');
$this->add_pattern('IDENT', '/[a-zA-Z_]\w*/');
$this->add_pattern('STRING', LuminousTokenPresets::$DOUBLE_STR);
$this->add_pattern('STRING', LuminousTokenPresets::$SINGLE_STR);
$this->add_pattern('FUNCTION', '/`(?>[^`\\\\]+|\\\\.)*(`|$)/s');
$this->add_identifier_mapping('FUNCTION', $GLOBALS['luminous_php_functions']);
$this->add_identifier_mapping('KEYWORD', $GLOBALS['luminous_php_keywords']);
$this->add_filter('STRING', array($this, 'str_filter'));
$this->add_filter('HEREDOC', array($this, 'str_filter'));
$this->add_filter('NOWDOC', array($this, 'nowdoc_filter'));
}
static function str_filter($token) {
if ($token[1][0] !== '"' && $token[0] !== 'HEREDOC') return $token;
elseif(strpos($token[1], '$') === false) return $token;
$token = LuminousUtils::escape_token($token);
// matches $var, ${var} and {$var} syntax
$token[1] = preg_replace('/
(?: \$\{ | \{\$ ) [^}]++ \}
|
\$\$?[a-zA-Z_]\w*
/x', '<VARIABLE>$0</VARIABLE>',
$token[1]);
return $token;
}
static function nowdoc_filter($token) {
$token[0] = 'HEREDOC';
return $token;
}
function main() {
$this->start();
while (!$this->eos()) {
$tok = null;
$index = $this->pos();
if (($match = $this->next_match()) !== null) {
$tok = $match[0];
if ($match[1] > $index) {
$this->record(substr($this->string(), $index, $match[1] - $index), null);
}
} else {
$this->record($this->rest(), null);
$this->terminate();
break;
}
if ($tok === 'TERM') {
$this->unscan();
break;
}
if($tok === 'IDENT') {
// do the user defns here, i.e. class XYZ extends/implements ABC
// or function XYZ
$m = $this->match();
$this->record($m, 'IDENT');
if (($m === 'class' || $m === 'function' || $m === 'extends' || $m === 'implements')
&& $this->scan('/(\s+)([a-zA-Z_]\w*)/') )
{
$this->record($this->match_group(1), null);
$this->record($this->match_group(2), 'USER_FUNCTION');
$this->user_defs[$this->match_group(2)] = ($m === 'function')? 'FUNCTION'
: 'TYPE';
}
continue;
}
elseif($tok === 'OPERATOR') {
// figure out heredoc syntax here
if (strpos($this->match(), '<<<') !== false) {
$this->record($this->match(), $tok);
$this->scan('/([\'"]?)([\w]*)((?:\\1)?)/');
$g = $this->match_groups();
$nowdoc = false;
if ($g[1]) {
// nowdocs are delimited by single quotes. Heredocs MAY be
// delimited by double quotes, or not.
$nowdoc = $g[1] === "'";
$this->record($g[1], null);
}
$delimiter = $g[2];
$this->record($delimiter, 'KEYWORD');
if ($g[3]) $this->record($g[3], null);
// bump us to the end of the line
if (strlen($this->scan('/.*/')))
$this->record($this->match(), null);
if ($this->scan_until("/^$delimiter|\z/m")) {
$this->record($this->match(), ($nowdoc)? 'NOWDOC' : 'HEREDOC');
if ($this->scan('/\w+/'))
$this->record($this->match(), 'KEYWORD');
}
continue;
}
}
assert($this->pos() > $index);
$this->record($this->match(), $tok);
}
}
}
/*
* This is a controller class which handles alternating between PHP and some
* other language (currently HTML only, TODO allow plain text as well)
* PHP and the other language are handled by subscanners
*/
class LuminousPHPScanner extends LuminousScanner {
/// the 'non-php' scanner
protected $subscanner;
/// the real php scanner
protected $php_scanner;
/// If it's a snippet, we assume we're starting in PHP mode.
public $snippet = false;
function __construct($src=null) {
$this->subscanner = new LuminousHTMLScanner($src);
$this->subscanner->embedded_server = true;
$this->subscanner->init();
$this->php_scanner = new LuminousPHPSubScanner($src);
$this->php_scanner->init();
parent::__construct($src);
}
function string($s=null) {
if ($s !== null) {
$this->subscanner->string($s);
$this->php_scanner->string($s);
}
return parent::string($s);
}
protected function scan_php($delimiter) {
if ($delimiter !== null)
$this->record($delimiter, 'DELIMITER');
$this->php_scanner->pos($this->pos());
$this->php_scanner->main();
$this->record($this->php_scanner->tagged(),
($delimiter === '<?=')? 'INTERPOLATION' : null, true);
$this->pos($this->php_scanner->pos());
assert($this->eos() || $this->check('/\\?>/'));
if ($this->scan('/\\?>/'))
$this->record($this->match(), 'DELIMITER');
}
protected function scan_child() {
$this->subscanner->pos($this->pos());
$this->subscanner->main();
$this->pos($this->subscanner->pos());
assert($this->eos() || $this->check('/<\\?/'));
$this->record($this->subscanner->tagged(), null, true);
}
function main() {
while (!$this->eos()) {
$p = $this->pos();
if ($this->snippet)
$this->scan_php(null);
elseif ($this->scan('/<\\?(?:php|=)?/'))
$this->scan_php($this->match());
else
$this->scan_child();
assert($this->pos() > $p);
}
}
static function guess_language($src, $info) {
// cache p because this function is hit by the snippet scanner as well
static $p = 0.0;
static $src_ = null;
if ($src_ === $src) {
return $p;
}
// look for delimiter tags
if (strpos($src, '<?php') !== false) $p += 0.5;
elseif (preg_match('/<\\?(?!xml)/', $src)) $p += 0.20;
// check for $this, self:: parent::
if (preg_match('/\\$this\\b|((?i: self|parent)::)/x', $src)) $p += 0.15;
// check for PHP's OO notation: $somevar->something
if (preg_match('/\\$[a-z_]\w*+->[a-z_]/i', $src)) $p += 0.05;
// check for some common functions:
if (preg_match('/\\b(echo|require(_once)?|include(_once)?|preg_\w)/i',
$src)) $p += 0.05;
$src_ = $src;
return $p;
}
}
class LuminousPHPSnippetScanner extends LuminousPHPScanner {
public $snippet = true;
public static function guess_language($src, $info) {
$p = parent::guess_language($src, $info);
if ($p > 0.0) {
// look for the close/open tags, if there is no open tag, or if
// there is a close tag before an open tag, then we guess we're
// in a snippet
// if we are in a snippet we need to come out ahead of php, and
// if we're not then we need to be behind it.
$open_tag = strpos($src, '<?');
$close_tag = strpos($src, '?>');
if ($open_tag === false ||
($close_tag !== false && $close_tag < $open_tag))
{
$p += 0.01;
}
else $p -= 0.01;
}
return $p;
}
}

View file

@ -0,0 +1,412 @@
<?php
/*
* Python scanner - includes Django
*
* TODO: Django does not respect {% comment %} ... {% endcomment %}
*/
class LuminousPythonScanner extends LuminousScanner {
public $django = false;
public function init() {
$this->remove_filter('comment-to-doc');
// so it turns out this template isn't quite as readable as I hoped, but
// it's a triple string, e.g:
// "{3} (?: [^"\\]+ | ""[^"\\]+ | "[^"\\]+ | \\.)* (?: "{3}|$)
$triple_str_template = '%1$s{3} (?> [^%1$s\\\\]+ | %1$s%1$s[^%1$s\\\\]+ | %1$s[^%1$s\\\\]+ | \\\\. )* (?: %1$s{3}|$)';
$str_template = '%1$s (?> [^%1$s\\\\]+ | \\\\. )* (?: %1$s|$)';
$triple_dstr = sprintf($triple_str_template, '"');
$triple_sstr = sprintf($triple_str_template, "'");
$this->add_pattern('IDENT', '/[a-zA-Z_](?>\w*)(?!["\'])/');
// I *assume* that Django tags terminate these
$this->add_pattern('COMMENT', sprintf('/\#.*%s/',
$this->django? '(?=[%}]\})' : ''));
// decorator
$this->add_pattern('TYPE', '/@(\w+\.?)+/');
// Python strings may be prefixed with r (raw) or u (unicode).
// This affects how it handles backslashes, but I don't *think* it
// affects escaping of quotes....
$this->add_pattern('STRING', "/[RUru]?$triple_dstr/xs");
$this->add_pattern('STRING', "/[RUru]?$triple_sstr/xs");
$this->add_pattern('STRING', "/[RUru]?" . sprintf($str_template, '"') . '/sx');
$this->add_pattern('STRING', "/[RUru]?" . sprintf($str_template, "'") . '/xs');
// EPIC.
$this->add_pattern('NUMERIC', '/
#hex
(?:0[xX](?>[0-9A-Fa-f]+)[lL]*)
|
# binary
(?:0[bB][0-1]+)
|
#octal
(?:0[oO0][0-7]+)
|
# regular number
(?:
(?>[0-9]+)
(?:
# long identifier
[lL]
|
# Or a fractional part, which may be imaginary
(?:
(?:\.?(?>[0-9]+)?
(?:(?:[eE][\+\-]?)?(?>[0-9]+))?
)[jJ]?
)
)?
)
|
(
# or only after the point, float x = .1;
\.(?>[0-9]+)(?:(?:[eE][\+\-]?)?(?>[0-9]+))?[jJ]?
)
/x');
// %} and }} are django terminators
if ($this->django) {
$this->add_pattern('TERM', '/[%}]\}/');
}
// catch the colon separately so we can use $match === ':' in figuring out
// where docstrs occur
$this->add_pattern('OPERATOR', '/\+=|-=|\*=|\/=|>=|<=|!=|==|\*\*|[!%^*\-=+;<>\\\\(){}\[\],\\.:]/');
if ($this->django) {
// Django specific keywords
// https://docs.djangoproject.com/en/1.3/ref/templates/builtins/
$this->add_identifier_mapping('KEYWORD', array('autoescape',
'endautoescape', 'cycle', 'filter', 'endfilter', 'include',
'extends', 'firstof', 'empty', 'ifchanged', 'endifchanged',
'ifequal', 'endifequal', 'ifnotequal', 'endifnotequal',
'load', 'now', 'regroup', 'spaceless', 'endspaceless',
'ssi', 'url', 'widthratio', 'endwith',
'endfor', 'endif',
'endwhile'));
}
$this->add_identifier_mapping('KEYWORD', array('assert', 'as', 'break',
'class', 'continue', 'del', 'def', 'elif', 'else', 'except', 'exec',
'finally', 'for', 'from', 'global', 'if', 'import', 'lambda',
'print', 'pass', 'raise', 'return', 'try', 'while', 'yield',
'with',
'and', 'not', 'in', 'is', 'or', 'print'));
$this->add_identifier_mapping('FUNCTION', array('all', 'abs', 'any',
'basestring', 'bin', 'callable', 'chr', 'classmethod', 'cmp', 'compile',
'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'file', 'filter',
'format',
'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex',
'id', 'input', 'isinstance', 'issubclass', 'iter', 'len', 'locals', 'map',
'max', 'min', 'memoryview', 'next', 'object', 'oct', 'open', 'ord', 'pow',
'property', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed',
'round', 'setattr', 'slice', 'sorted', 'staticmethod', 'sum', 'super',
'type', 'unichr', 'vars', 'xrange', 'zip', '__import__',
'bytearray', 'complex', 'dict', 'float', 'int', 'list', 'long',
'set', 'str', 'tuple', 'unicode', 'apply', 'buffer', 'coerce', 'intern'
));
// http://docs.python.org/library/exceptions.html
$this->add_identifier_mapping('TYPE',
array('BaseException', 'SystemExit',
'KeyboardInterrupt', 'GeneratorExit', 'Exception', 'StopIteration',
'StandardError', 'BufferError', 'ArithmeticError',
'FloatingPointError', 'OverflowError', 'ZeroDivisionError',
'AssertionError',
'AttributeError', 'EnvironmentError', 'IOError', 'OSError',
'WindowsError(Windows)', 'VMSError(VMS)', 'EOFError', 'ImportError',
'LookupError', 'IndexError', 'KeyError', 'MemoryError', 'NameError',
'UnboundLocalError', 'ReferenceError', 'RuntimeError',
'NotImplementedError',
'SyntaxError', 'IndentationError', 'TabError', 'SystemError', 'TypeError',
'ValueError', 'UnicodeError', 'UnicodeDecodeError', 'UnicodeEncodeError',
'UnicodeTranslateError', 'Warning', 'DeprecationWarning',
'PendingDeprecationWarning', 'RuntimeWarning', 'SyntaxWarning',
'UserWarning',
'FutureWarning', 'ImportWarning', 'UnicodeWarning', 'BytesWarning'));
$this->add_identifier_mapping('VALUE', array('False', 'None', 'self',
'True'));
}
// mini-scanner to handle highlighting module names in import lines
private function import_line() {
$import = false;
$from = false;
while(!$this->eol()) {
$c = $this->peek();
$tok = null;
$m = null;
if ($c === '\\') $m = $this->get(2);
elseif($this->scan('/[,\\.;\\*]+/')) $tok = 'OPERATOR';
elseif($this->scan("/[ \t]+/")){}
elseif(($m = $this->scan('/import\\b|from\\b/'))){
if ($m === 'import') $import = true;
elseif($m === 'from') $from = true;
else assert(0);
$tok = 'IDENT';
}
elseif($this->scan('/[_a-zA-Z]\w*/')) {
assert($from || $import);
// from module import *item*, or just import *item*
if ($import) {
$tok = 'USER_FUNCTION';
$this->user_defs[$this->match()] = 'TYPE';
}
// from *module* ...[import item], the module is not imported
else $tok = 'IDENT';
}
else break;
$this->record(($m !== null)? $m : $this->match(), $tok);
}
}
function main() {
$definition = false;
$doccstr = false;
$expect = '';
while (!$this->eos()) {
$tok = null;
$index = $this->pos();
if (($rule = $this->next_match()) !== null) {
$tok = $rule[0];
if ($rule[1] > $index) {
$this->record(substr($this->string(), $index, $rule[1] - $index), null);
}
} else {
$this->record(substr($this->string(), $index), null);
$this->terminate();
break;
}
// Django terminator tag - break to superscanner
if ($tok === 'TERM') {
$this->unscan();
break;
}
$m = $this->match();
/* python doc strs are a pain because they're actually just strings.
* Also, I'm pretty sure a string in a non-interesting place just counts
* as a no-op and is also used as a comment sometimes
* So we've got something a bit complicated going on here: if we meet
* a 'class' or a 'def' (function def) then we wait until the next ':'
* and say "we expect a doc-str now". If the next token is not a string,
* we discard that state.
*
* similarly, if we meet a string which isn't a doc-str, we look behind
* and expect to see an operator or open bracket, else it's a comment.
* NOTE: we class ':' as a legal string preceding char because it's used
* as dictionary key:value separators. This will fail on the case:
*
* while 1:
* "do something"
* break
*
*
* NOTE: note we're skipping whitespace.
* NOTE: we disable the no-op detection for Django because the string
* might be inside an output tag.
*
*/
if ($definition && $doccstr) {
if($tok === 'STRING')
$tok = 'COMMENT';
}
elseif ($tok === 'STRING' && !$this->django) {
$i = count($this->tokens);
$tok = 'COMMENT';
while ($i--) {
$t = $this->tokens[$i][0];
$s = $this->tokens[$i][1];
if ($t === null || $t === 'COMMENT') continue;
elseif ($t === 'OPERATOR' || $t === 'IDENT' || $t === 'NUMERIC') {
$tok = 'STRING';
}
break;
}
// finally, if we can look ahead to a binary operator, or so,
// we concede it probably is a string
if ($tok === 'COMMENT') {
if ($this->check('/\s*(?: [+:&.,] | (?:and|or|is|not)\\b)/x'))
$tok = 'STRING';
}
}
// reset this; if it didn't catch above then it's not valid now.
if ($definition && $doccstr) {
$definition = false;
$doccstr = false;
}
if ($tok === 'IDENT') {
if ($m === 'import' || $m === 'from') {
$this->unscan();
$this->import_line();
continue;
}
// these are definition keywords, the next token should be an
// identifier, which is a user-defined type or function
if ($m === 'class' || $m === 'def') {
$definition = true;
$expect = 'user_def';
}
// this is caught on the next iteration
elseif($expect === 'user_def') {
$tok = 'USER_FUNCTION';
$expect = false;
$this->user_defs[$m] = 'FUNCTION';
}
}
else {
// if this hasn't caught, it's not valid
$expect = false;
}
if ($definition && $m === ':') {
$doccstr = true;
}
$this->record($m, $tok);
}
}
public static function guess_language($src, $info) {
if (strpos($info['shebang'], 'python') !== false) return 1.0;
if ($info['shebang']) return 0.0;
$p = 0.0;
// let's look for some trademark pythonic constructs, although I
// have a feeling that recent versions of ECMA also impelment some
// of this
if (preg_match('/^\s*+ for \s++ \w++ \s++ in \s++ \w++ \s*+ :/xm', $src))
$p += 0.05;
if (preg_match('/True|False|None/', $src)) $p += 0.01;
if (preg_match('/"{3}|\'{3}/', $src)) $p += 0.05;
// class something(object)
//
if (preg_match('/^\s*+ class \s++ \w++ \s*+ \( \s*+ object \s*+ \)/xm',
$src)) $p += 0.1;
// def __init__ (constructor)
if (preg_match('/\\bdef \s++ __init__\\b/x', $src)) $p += 0.2;
// method decorators
if (preg_match("/^\s*+ @[\w\\.]++ .*+ [\n\r]++ \s*+ def\\b/mx", $src))
$p += 0.1;
// pmax = 0.41
// common imports: import os|sys|re
if (preg_match('/^import\s++(os|sys|re)\\b/m', $src))
$p += 0.05;
// from x import y
if (preg_match('/^\s*+ from \s++ (?:\w++(?:\.\w++)*+) \s++ import \s/xm',
$src))
$p += 0.10;
return $p;
}
}
class LuminousDjangoScanner extends LuminousScanner {
// warning: some copying and pasting with the rails scanner here
// HTML scanner has to be persistent.
private $html_scanner;
public function init() {
$this->html_scanner = new LuminousHTMLScanner();
$this->html_scanner->string($this->string());
$this->html_scanner->embedded_server = true;
$this->html_scanner->server_tags = '/\{[{%#]/';
$this->html_scanner->init();
}
public function scan_html() {
$this->html_scanner->pos($this->pos());
$this->html_scanner->main();
$this->record($this->html_scanner->tagged(), null, true);
$this->pos($this->html_scanner->pos());
}
public function scan_python($short=false) {
$python_scanner = new LuminousPythonScanner($this->string());
$python_scanner->django = true;
$python_scanner->init();
$python_scanner->pos($this->pos());
$python_scanner->main();
$this->record($python_scanner->tagged(), $short? 'INTERPOLATION' : null, true);
$this->pos($python_scanner->pos());
}
public function main() {
while(!$this->eos()) {
$p = $this->pos();
// django's tags are {{ }} and {% %}
// there's also a {# #} comment tag but we can probably handle that here
// more easily
// same for {% comment %} ... {% endcomment %}
if ($this->scan('/\{([{%])/')) {
$match = $this->match();
$m1 = $this->match_group(1);
// {% comment %} ... {% endcomment %}
if ($this->scan('/\s*comment\s*%\}/')) {
$match .= $this->match();
$end_pattern = '/\{%\s*endcomment\s*%\}/';
if ($this->scan_until($end_pattern) !== null) {
$match .= $this->match();
$match .= $this->scan($end_pattern);
}
else {
$match .= $this->rest();
$this->terminate();
}
$this->record($match, 'COMMENT');
}
// {{ ... }} or {% ... %}
else {
$this->record($match, 'DELIMITER');
$this->scan_python($m1 === '{');
if ($this->scan('/[}%]\}/')) {
$this->record($this->match(), 'DELIMITER');
}
}
// {# ... #}
} elseif($this->scan('/\{\# (?: [^\#]++ | \#(?! \} ) )*+ (?: \#\} | $)/x')) {
$this->record($this->match(), 'COMMENT');
}
else {
$this->scan_html();
}
assert($p < $this->pos());
}
}
public static function guess_language($src, $info) {
if (($html = LuminousHTMLScanner::guess_language($src, $info)) >= 0.2) {
if (strpos($src, '{{') !== false || strpos($src, '{%') !== false)
return $html + 0.01;
}
return 0.0;
}
}

View file

@ -0,0 +1,70 @@
<?php
/*
* Rails. Basically a wrapper around Ruby and HTML.
*/
class LuminousRailsScanner extends LuminousScanner {
// HTML scanner has to be persistent. Ruby doesn't.
private $html_scanner;
public function init() {
$this->html_scanner = new LuminousHTMLScanner();
$this->html_scanner->string($this->string());
$this->html_scanner->embedded_server = true;
$this->html_scanner->server_tags = '/<%/';
$this->html_scanner->init();
}
public function scan_html() {
$this->html_scanner->pos($this->pos());
$this->html_scanner->main();
$this->record($this->html_scanner->tagged(), null, true);
$this->pos($this->html_scanner->pos());
}
public function scan_ruby($short=false) {
$ruby_scanner = new LuminousRubyScanner($this->string());
$ruby_scanner->rails = true;
$ruby_scanner->init();
$ruby_scanner->pos($this->pos());
$ruby_scanner->main();
$this->record($ruby_scanner->tagged(), $short? 'INTERPOLATION' : null, true);
$this->pos($ruby_scanner->pos());
}
public function main() {
while(!$this->eos()) {
$p = $this->pos();
if ($this->scan('/<%#?([\-=]?)/')) {
$this->record($this->match(), 'DELIMITER');
$this->scan_ruby($this->match_group(1) === '=');
if ($this->scan('/-?%>/')) {
$this->record($this->match(), 'DELIMITER');
}
}
else {
$this->scan_html();
}
assert($p < $this->pos());
}
}
public static function guess_language($src, $info) {
$p = LuminousRubyScanner::guess_language($src, $info);
if ($p > 0) {
if (preg_match('/<%.*%>/', $src)) $p += 0.02;
else $p = 0.0;
$p = min($p, 1);
}
return $p;
}
}

View file

@ -0,0 +1,522 @@
<?php
/*
* Ruby's grammar is basically insane. We're not going to aim to correctly
* highlight all legal Ruby code because we'll be here all year and we'll still
* get it wrong, but we're going to have a go at getting the standard stuff
* right as well as:
* heredocs
* balanced AND NESTED string/regex delimiters
* interpolation
*
* disclaimer: I don't actually know Ruby.
*
* Problem is that Ruby *appears* to have to disambiguate loads of stuff at
* runtime, which is frankly a little optimistic for a syntax highlighter.
* Ruby allows you to omit calling parantheses, so it's not practical (and
* impossible if the code snippet is incomplete) to figure out operator/operand
* position. e.g.
* x = y %r/z/x
* is x = y mod r div z div x, unless y is a function, in which case it's:
* x = y( /z/x ) where /z/x is a regex
*/
class LuminousRubyScanner extends LuminousScanner {
// set to true if this is a nested scanner which needs to exit if it
// encounters a } while nothing else is on the stack, i.e. it is being
// used to process an interpolated block
public $interpolation = false;
protected $curley_braces = 0; // poor man's curly brace stack.
public $rails = false;
// operators depend somewhat on whether or not rails is active, else we
// don't want to consume a '%' if it comes right before a '>', we want
// to leave that for the rails close-tag detection
private $operator_regex = null;
private $string_regex = null;
private $comment_regex = null;
// gaaah
private $numeric = '/
(?:
#control codes
(?:\?(?:\\\[[:alpha:]]-)*[[:alpha:]])
|
#hex
(?:0[xX](?>[0-9A-Fa-f]+)[lL]*)
|
# binary
(?:0[bB][0-1]+)
|
#octal
(?:0[oO0][0-7]+)
|
# regular number
(?:
(?>[0-9]+)
(?:
# fraction
(?:
(?:\.?(?>[0-9]+)?
(?:(?:[eE][\+\-]?)?(?>[0-9]+))?
)
)
)?
)
|
(
# or only after the point, float x = .1;
\.(?>[0-9]+)(?:(?:[eE][\+\-]?)?(?>[0-9]+))?
)
)
(?:_+\d+)*
/x';
/// queue of heredoc declarations which will need to be handled as soon as EOL is reached
/// each element is a tuple: (delimiter(str), identable?, interpolatable?)
private $heredocs = array();
public function init() {
$this->comment_regex =
$this->rails? "/ \# (?: [^\n%]*+ | %(?!>))* /x"
: '/#.*/';
// http://www.zenspider.com/Languages/Ruby/QuickRef.html#23
$this->operator_regex = '/
\? | ;
| ::? | \*[=\*]? | \/=? | -=? | %=? | ^=? | &&? | \|\|? | \.{2,3}
| \^=?
| < (?:=>|<|=)? | >=?
| =[>~] | ={1,3}
| \+=? | ![=~]?
/x';
// $this->operator_regex = '/(?: [~!^&*\-+=:;|<>\/?';
// if ($this->rails) $this->operator_regex .= ']+|%(?!>))+';
// else $this->operator_regex .= '%]+)';
// $this->operator_regex .= '/x';
$this->add_identifier_mapping('KEYWORD', array('BEGIN', 'END', 'alias',
'begin', 'break', 'case', 'class', 'def', 'defined?', 'do',
'else', 'elsif', 'end', 'ensure', 'for', 'if', 'module', 'next',
'redo', 'rescue', 'retry', 'return', 'self', 'super', 'then',
'undef', 'unless', 'until', 'when', 'while', 'yield',
'false', 'nil', 'self', 'true', '__FILE__', '__LINE__', 'TRUE', 'FALSE',
'NIL', 'STDIN', 'STDERR', 'ENV', 'ARGF', 'ARGV', 'DATA', 'RUBY_VERSION',
'RUBY_RELEASE_DATE', 'RUBY_PLATFORM',
'and', 'in', 'not', 'or',
'public', 'private', 'protected'
));
// http://www.tutorialspoint.com/ruby/ruby_builtin_functions.htm
// don't know how reliable that is... doesn't look incredibly inspiring
$this->add_identifier_mapping('FUNCTION', array('abord', 'Array',
'at_exit', 'autoload', 'binding', 'block_given?', 'callcc', 'caller',
'catch', 'chomp', 'chomp!', 'chop', 'chop!', 'eval', 'exec', 'exit',
'exit!', 'fail', 'Float', 'fork', 'format', 'gets', 'global_variables',
'gsub', 'gsub!', 'Integer', 'lambda', 'proc', 'load', 'local_variables',
'loop', 'open', 'p', 'print', 'printf', 'proc', 'puts', 'raise', 'fail',
'rand', 'readlines', 'require', 'scan', 'select', 'set_trace_func',
'sleep', 'split', 'sprintf', 'srand', 'String', 'syscall', 'system',
'sub', ',sub!', 'test', 'throw', 'trace_var', 'trap', 'untrace_var',
'abs', 'ceil', 'coerce', 'divmod', 'floor', 'integer?', 'modulo',
'nonzero?', 'remainder', 'round', 'truncate', 'zero?', 'chr', 'size',
'step', 'times', 'to_f', 'to_int', 'to_i', 'finite?', 'infinite?',
'nan?', 'atan2', 'cos', 'exp', 'frexp', 'ldexp', 'log', 'log10', 'sin',
'sqrt', 'tan'));
// this can break a bit with Ruby's whacky syntax
$this->remove_filter('pcre');
// don't want this.
$this->remove_filter('comment-to-doc');
$this->add_filter('REGEX', create_function('$tok',
'return LuminousFilters::pcre($tok, (isset($tok[1][0]) && $tok[1][0] === "/"));'));
}
protected function is_regex() {
/*
* Annoyingly I don't really know exactly what rules Ruby uses for
* disambiguating regular expressions. There might be some incorrect
* assumptions in here.
*/
if ($this->check('%/=\s%'))
return false;
$following_space = (bool)$this->check("%/[ \t]%");
$space = false;
for($i=count($this->tokens)-1; $i>=0; $i--) {
$tok = $this->tokens[$i];
if ($tok[0] === 'COMMENT') continue;
elseif ($tok[0] === 'OPERATOR') return true;
elseif($tok[0] === 'STRING') return true;
elseif ($tok[1] === '(' || $tok[1] === ',' || $tok[1] === '{' ||
$tok[1] === '[') {
// this is definitely an operand
return true;
}
elseif($tok[0] === null) {
$space = true;
continue;
}
elseif($tok[0] === 'NUMERIC') {
// this is definitely an operator
return false;
}
elseif ($tok[0] === 'IDENT'
|| $tok[0] === 'CONSTANT'
|| $tok[0] === 'VALUE' // aka :symbols
) {
// this could be an operator or operand
// Kate's syntax engine seems to operate on the following basis:
if ($space && $following_space) return false;
return $space;
}
return false;
}
return true; // no preceding tokens, presumably a code fragment.
}
protected function interpolate() {
$interpolation_scanner = new LuminousRubyScanner();
$interpolation_scanner->string($this->string());
$interpolation_scanner->pos($this->pos());
$interpolation_scanner->interpolation = true;
$interpolation_scanner->init();
$interpolation_scanner->main();
$this->record($interpolation_scanner->tagged(), 'INTERPOLATION', true);
$this->pos($interpolation_scanner->pos());
}
// handles the heredoc array. Call at eol/bol when the heredoc queue is
// not empty
protected function do_heredoc() {
assert (!empty($this->heredocs));
$start = $this->pos();
for($i=0; $i<count($this->heredocs) ; ) {
$top = $this->heredocs[$i];
list($ident, $identable, $interpolatable) = $top;
$searches = array(
sprintf('/^%s%s\\b/m', $identable? "[ \t]*" : '',
preg_quote($ident, '/'))
);
if ($interpolatable)
$searches[] = '/\#\{/';
list($next, $matches) = $this->get_next($searches);
if ($next === -1) {
// no match for end delim, run to EOS
$this->record(substr($this->string(), $start), 'HEREDOC');
$this->terminate();
break;
}
assert($matches !== null);
if ($matches[0] === '#{') { // interpolation, break heredoc and do that.
$this->pos($next);
$this->record(substr($this->string(), $start, $this->pos()-$start), 'HEREDOC');
$this->record($matches[0], 'DELIMITER');
$this->pos_shift(strlen($matches[0]));
$this->interpolate();
if ($this->peek() === '}')
$this->record($this->get(), 'DELIMITER');
$start = $this->pos();
}
else {
//
$this->pos($next);
$this->record(substr($this->string(), $start, $this->pos()-$start), 'HEREDOC');
$this->record($matches[0], 'DELIMITER');
$this->pos($next + strlen($matches[0]));
$start = $this->pos();
$i++;
}
// subscanner might have consumed all the string, in which case there's
// no point continuing
if ($this->eos()) break;
}
// we may or may not have technically addressed all the heredocs in the
// queue, but we do want to clear them out now
$this->heredocs = array();
}
private function record_string_range($from, $to, $type, $split) {
if ($to === $from) return;
$substr = substr($this->string(), $from, $to-$from);
if ($split) {
foreach(preg_split('/(\s+)/', $substr, -1, PREG_SPLIT_DELIM_CAPTURE) as $s) {
$type_ = preg_match('/^\s+$/', $s)? null : $type;
$this->record($s, $type_);
}
} else {
$this->record($substr, $type);
}
}
// handles string types (inc regexes), which may have nestable delimiters or
// interpolation.
// strdata is defined in the big ugly block in main()
// TODO: proper docs
protected function do_string($str_data) {
list($type, $open_delimiter, $close_delimiter, $pos, $interpolation,
$fancy_delim, $split) = $str_data;
$balanced = $open_delimiter !== $close_delimiter;
$template = '/(?<!\\\\)((?:\\\\\\\\)*)(%s)/';
$patterns = array();
$patterns['term'] = sprintf($template, preg_quote($close_delimiter, '/'));
if ($balanced) {
// for nesting balanced delims
$patterns['nest'] = sprintf($template, preg_quote($open_delimiter, '/'));
}
if ($interpolation) {
$patterns['interp'] = sprintf($template, preg_quote('#{', '/'));
}
$nesting_level = 0;
$break = false;
while (!$break) {
list($name, $index, $matches) = $this->get_next_named($patterns);
if ($name === null) {
// special case, no matches, record the rest of the string and break
// immediately
$this->record_string_range($pos, strlen($this->string()), $type, $split);
$this->terminate();
break;
}
elseif ($name === 'nest') {
// nestable opener
$nesting_level++;
$this->pos( $index + strlen($matches[0]) );
}
elseif($name === 'term') {
// terminator, may be nested
if ($nesting_level === 0) {
// wasn't nested, real terminator.
if ($fancy_delim) {
// matches[1] is either empty or a sequence of backslashes
$this->record_string_range($pos, $index+strlen($matches[1]), $type, $split);
$this->record($matches[2], 'DELIMITER');
} else {
$this->record_string_range($pos, $index+strlen($matches[0]), $type, $split);
}
$break = true;
}
else {
// pop a nesting level
$nesting_level--;
}
$this->pos( $index + strlen($matches[0]) );
}
elseif($name === 'interp') {
// interpolation - temporarily break string highlighting, then
// do interpolation, then resume.
$this->record_string_range($pos, $index + strlen($matches[1]), $type, $split);
$this->record($matches[2], 'DELIMITER');
$this->pos( $index + strlen($matches[0]) );
$this->interpolate();
if (($c = $this->peek()) === '}')
$this->record($this->get(), 'DELIMITER');
$pos = $this->pos();
}
else {
assert(0);
}
if ($break) break;
}
if ($type === 'REGEX' && $this->scan('/[iomx]+/'))
$this->record($this->match(), 'KEYWORD');
}
public function main() {
while (!$this->eos()) {
if ($this->bol() && !empty($this->heredocs)) {
$this->do_heredoc();
}
if ($this->interpolation) {
$c = $this->peek();
if ($c === '{') $this->curley_braces++;
elseif($c === '}') {
$this->curley_braces--;
if ($this->curley_braces <= 0) { break;}
}
}
if ($this->rails && $this->check('/-?%>/')) {
break;
}
$c = $this->peek();
if ($c === '=' && $this->scan('/^=begin .*? (^=end|\\z)/msx')) {
$this->record($this->match(), 'DOCCOMMENT');
}
elseif($c === '#' && $this->scan($this->comment_regex))
$this->record($this->match(), 'COMMENT');
elseif($this->scan($this->numeric) !== null) {
$this->record($this->match(), 'NUMERIC');
}
elseif( $c === '$' && $this->scan('/\\$
(?:
(?:[!@`\'\+1~=\/\\\,;\._0\*\$\?:"&<>])
|
(?: -[0adFiIlpvw])
|
(?:DEBUG|FILENAME|LOAD_PATH|stderr|stdin|stdout|VERBOSE)
)/x') || $this->scan('/(\\$|@@?)\w+/')) {
$this->record($this->match(), 'VARIABLE');
}
elseif($this->scan('/:\w+/')) {
$this->record($this->match(), 'VALUE');
}
elseif ( $c === '<' && $this->scan('/(<<(-?))([\'"`]?)([A-Z_]\w*)(\\3)/i')) {
$m = $this->match_groups();
$this->record($m[0], 'DELIMITER');
$hdoc = array($m[4], $m[2] === '-', $m[3] !== "'");
$this->heredocs[] = $hdoc;
}
// TODO: "% hello " is I think a valid string, using whitespace as
// delimiters. We're going to disallow this for now because
// we're not disambiguating between that and modulus
elseif (($c === '"' || $c === "'" || $c === '`' || $c === '%') &&
$this->scan('/[\'"`]|%( [qQrswWx](?![[:alnum:]]|$) | (?![[:alnum:]\s]|$))/xm')
|| ($c === '/' && $this->is_regex())
)
{
$interpolation = false;
$type = 'STRING';
$delimiter;
$pos;
$fancy_delim = false;
$split = false;
if ($c === '/') {
$interpolation = true;
$type = 'REGEX';
$delimiter = $c;
$pos = $this->pos();
$this->get();
} else {
$pos = $this->match_pos();
$delimiter = $this->match();
if ($delimiter === '"') {
$interpolation = true;
} elseif($delimiter === "'") {}
elseif($delimiter === '`') {
$type = 'FUNCTION';
}
else {
$delimiter = $this->get();
$m1 = $this->match_group(1);
if ($m1 === 'Q' || $m1 === 'r' || $m1 === 'W' || $m1 === 'x')
$interpolation = true;
if ($m1 === 'w' || $m1 === 'W')
$split = true;
if ($m1 === 'x') $type = 'FUNCTION';
elseif($m1 === 'r') $type = 'REGEX';
$fancy_delim = true;
$this->record($this->match() . $delimiter, 'DELIMITER');
$pos = $this->pos();
}
}
$data = array($type, $delimiter, LuminousUtils::balance_delimiter($delimiter),
$pos, $interpolation, $fancy_delim, $split);
$this->do_string($data);
}
elseif( (ctype_alpha($c) || $c === '_') &&
($m = $this->scan('/[_a-zA-Z]\w*[!?]?/')) !== null) {
$this->record($m, ctype_upper($m[0])? 'CONSTANT' : 'IDENT');
if ($m === '__END__') {
if (!$this->interpolation) {
$this->record($this->rest(), null);
$this->terminate();
}
break;
}
}
elseif($this->scan($this->operator_regex))
$this->record($this->match(), 'OPERATOR');
elseif($this->scan("/[ \t]+/")) $this->record($this->match(), null);
else {
$this->record($this->get(), null);
}
}
// In case not everything was popped
if (isset($this->state_[0])) {
$this->record(
substr($this->string(), $this->state_[0][3],
$this->pos() - $this->state_[0][3]),
$this->state_[0][0]
);
$this->terminate();
}
}
public static function guess_language($src, $info) {
if (strpos($info['shebang'], 'ruby') !== false) return 1.0;
elseif($info['shebang']) return 0;
$p = 0;
if (strpos($src, 'nil')) $p += 0.05;
if (strpos($src, '.nil?')) $p += 0.02;
if (strpos($src, '.empty?')) $p += 0.02;
// interpolation
if (strpos($src, '#{$')) $p += 0.02;
// @ and $ vars
if (preg_match('/@[a-zA-Z_]/', $src) && preg_match('/\\$[a-zA-Z_]/', $src))
$p += 0.02;
// symbols
if (preg_match('/:[a-zA-Z_]/', $src)) $p += 0.01;
// func def - no args
if (preg_match("/^\s*+def\s++[a-zA-Z_]\w*+[ \t]*+[\n\r]/m", $src))
$p += 0.1;
// {|x[,y[,z...]]| is a very ruby-like construct
if (preg_match('/ \\{ \\|
\s*+ [a-zA-Z_]\w*+ \s*+
(,\s*+[a-zA-Z_]\w*+\s*+)*+
\\|/x', $src))
$p += 0.15;
// so is 'do |x|'
if (preg_match("/\\bdo\s*+\\|[^\\|\r\n]++\\|/", $src))
$p += 0.05;
// class defs with inheritance has quite distinct syntax
// class x < y
if (preg_match(
"/^ \s* class \s+ \w+ \s* < \s* \w+(::\w+)* [\t ]*+ [\r\n] /mx",
$src))
$p += 0.1;
$num_lines = $info['num_lines'];
// let's say if 5% of lines are hash commented that's a good thing
if (substr_count($src, '#') > $num_lines/20) $p += 0.05;
// =~ /regex/
if (preg_match('%=~\s++/%', $src)) $p += 0.02;
if (preg_match('/unless\s+[^\?]++\?/', $src)) $p += 0.05;
if (preg_match('/^(\s*+)def\s+.*^\1end\s/ms', $src)) $p += 0.05;
if (preg_match('/\.to_\w+(?=\s|$)/', $src)) $p += 0.01;
return $p;
}
}

View file

@ -0,0 +1,152 @@
<?php
/**
* Scala
*
* Direct port of old luminous language file.
*
* TODO: The XML literals may contain embedded scala code. This is bad
* because we ignore that currently, and we may, in rare circumstances,
* incorrectly pop a tag when in fact it's inside a scala expression
*
* Some comments reference section numbers of the scala spec:
* http://www.scala-lang.org/sites/default/files/linuxsoft_archives/docu/files/ScalaReference.pdf
*
*/
// scala inherits some stuff from Java
require_once(dirname(__FILE__) . '/include/java_func_list.php');
class LuminousScalaScanner extends LuminousSimpleScanner {
/**
* Multiline comments nest
*/
function comment_override() {
$this->nestable_token('COMMENT', '%/\\*%', '%\\*/%');
}
/**
* Scala has XML literals.
*/
function xml_override($matches) {
// this might just be an inequality, so we first need to disambiguate
// that
// 1.5 - the disambiguation is pretty simple, an XML tag must
// follow either whitespace, (, or {, and the '<' must be followed
// by '[!?_a-zA-Z]
// I'm not sure if a comment is a special case, or if it's treated as
// whitespace...
$xml = false;
for($i=count($this->tokens)-1; $i>=0; $i--) {
$tok = $this->tokens[$i];
$name = $tok[0];
// ... but we're going treat it as a no-op and skip over it
if ($name === 'COMMENT') continue;
$last_char = $tok[1][strlen($tok[1])-1];
if (!(ctype_space($last_char) || $last_char === '(' ||
$last_char === '{')) break;
if (!$this->check('/<[!?a-zA-Z0-9_]/')) break;
$xml = true;
}
if (!$xml) {
$this->record($matches[0], 'OPERATOR');
$this->pos_shift(strlen($matches[0]));
return;
}
$subscanner = new LuminousXMLScanner();
$subscanner->string($this->string());
$subscanner->pos($this->pos());
$subscanner->xml_literal = true;
$subscanner->init();
$subscanner->main();
$tagged = $subscanner->tagged();
$this->record($tagged, 'XML', true);
$this->pos($subscanner->pos());
}
function init() {
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_SL);
$this->add_pattern('COMMENT_ML', '%/\\*%');
$this->overrides['COMMENT_ML'] = array($this, 'comment_override');
// 1.3.1 integer literals, 1.3.2 floatingPointLiteral
// Do the float first so it takes precedence, our scanner does not follow
// the max-munch rule
$digit = '\d';
$exp = '(?:[eE][+-]?\d+)';
$suffix = '[FfDd]';
$this->add_pattern('NUMERIC', "/(?: \d+\\.\d* | \\.\d+) $exp? $suffix? /x");
$this->add_pattern('NUMERIC', "/\d+($exp $suffix? |$exp?$suffix)/x");
$this->add_pattern('NUMERIC', '/(?:0x[a-fA-F0-9]+|\d+)[lL]?/');
// 1.3.4 character literals
// we can't really parse the unicode and work out what's printable,
// so we'll just allow any unicode sequence
$this->add_pattern('CHARACTER',
"/'
(
(?:\\\\ (?:u[a-f0-9]{1,4}|\d+|.))
| .
)'/sx");
// 1.3.5 - 1.3.6
// strings are kind of pythonic, triple quoting makes them multiline
$this->add_pattern('STRING', '/"""
(?: [^"\\\\]+ | \\\\. | ""[^"] | "[^"])*
(?:"""|$)/sx');
$this->add_pattern('STRING', LuminousTokenPresets::$DOUBLE_STR_SL);
$this->add_pattern('lt', '/</');
$this->overrides['lt'] = array($this, 'xml_override');
$this->add_pattern('OPERATOR', '/[¬!%^&*-=+~;:|>\\/?\\\\]+/');
$this->add_pattern('IDENT', '/[a-z_]\w*/i');
// 1.3.3 boolean literals
$this->add_identifier_mapping('VALUE', array('true', 'false', 'null', 'None'));
// from old luminous file
$this->add_identifier_mapping('KEYWORD', array('abstract', 'case',
'catch', 'class', 'def', 'do', 'else', 'extends', 'final', 'finally',
'for', 'forSome', 'if', 'implicit', 'import', 'lazy', 'match',
'new', 'object', 'override', 'package', 'private', 'protected',
'return', 'sealed', 'super', 'this', 'throw', 'trait', 'try', 'type',
'val', 'var', 'while', 'with', 'yield'));
$this->add_identifier_mapping('TYPE', array('boolean', 'byte', 'char',
'dobule', 'float', 'int', 'long', 'string', 'short', 'unit',
'Boolean', 'Byte', 'Char', 'Double', 'Float', 'Int', 'Long', 'String',
'Short', 'Unit'));
// from Kate's syntax file
$this->add_identifier_mapping('TYPE', array('ActorProxy', 'ActorTask',
'ActorThread', 'AllRef', 'Any', 'AnyRef', 'Application', 'AppliedType',
'Array', 'ArrayBuffer', 'Attribute', 'BoxedArray', 'BoxedBooleanArray',
'BoxedByteArray', 'BoxedCharArray', 'Buffer', 'BufferedIterator', 'Char',
'Console', 'Enumeration', 'Fluid', 'Function', 'IScheduler',
'ImmutableMapAdaptor', 'ImmutableSetAdaptor', 'Int', 'Iterable', 'List',
'ListBuffer', 'None', 'Option', 'Ordered', 'Pair', 'PartialFunction',
'Pid', 'Predef', 'PriorityQueue', 'PriorityQueueProxy', 'Reaction',
'Ref', 'Responder', 'RichInt', 'RichString', 'Rule', 'RuleTransformer',
'Script', 'Seq', 'SerialVersionUID', 'Some', 'Stream', 'Symbol',
'TcpService', 'TcpServiceWorker', 'Triple', 'Unit', 'Value',
'WorkerThread', 'serializable', 'transient', 'volatile'));
$this->add_identifier_mapping('TYPE', $GLOBALS['luminous_java_types']);
}
public static function guess_language($src, $info) {
$p = 0;
// func def, a lot like python
if (preg_match('/\\bdef\s+\w+\s*\(/', $src)) $p += 0.05;
// val x = y
if (preg_match('/\\bval\s+\w+\s*=/', $src)) $p += 0.1;
// argument types
if (preg_match('/\\(\s*\w+\s*:\s*(String|Int|Array)/', $src)) $p += 0.05;
// tripled quoted strings, like python
if (preg_match('/\'{3}|"{3}/', $src)) $p += 0.05;
return $p;
}
}

View file

@ -0,0 +1,335 @@
<?php
/**
* The SCSS scanner is quite complex, having to deal with nested rules
* and so forth and some disambiguation is non-trivial, so we are employing
* a two-pass approach here - we first tokenize the source as normal with a
* scanner, then we parse the token stream with a parser to figure out
* what various things really are.
*/
class LuminousSCSSScanner extends LuminousScanner {
private $regexen = array();
public $rule_tag_map = array(
'PROPERTY' => 'TYPE',
'COMMENT_SL' => 'COMMENT',
'COMMENT_ML' => 'COMMENT',
'ELEMENT_SELECTOR' => 'KEYWORD',
'STRING_S' => 'STRING',
'STRING_D' => 'STRING',
'CLASS_SELECTOR' => 'VARIABLE',
'ID_SELECTOR' => 'VARIABLE',
'PSEUDO_SELECTOR' => 'OPERATOR',
'ATTR_SELECTOR' => 'OPERATOR',
'WHITESPACE' => null,
'COLON' => 'OPERATOR',
'SEMICOLON' => 'OPERATOR',
'COMMA' => 'OPERATOR',
'R_BRACE' => 'OPERATOR',
'R_BRACKET' => 'OPERATOR',
'R_SQ_BRACKET' => 'OPERATOR',
'L_BRACE' => 'OPERATOR',
'L_BRACKET' => 'OPERATOR',
'L_SQ_BRACKET' => 'OPERATOR',
'OTHER_OPERATOR' => 'OPERATOR',
'GENERIC_IDENTIFIER' => null,
'AT_IDENTIFIER' => 'KEYWORD',
'IMPORTANT' => 'KEYWORD',
);
public function init() {
$this->regexen = array(
// For the first pass we just feed in a bunch of tokens.
// Some of these are generic and will require disambiguation later
'COMMENT_SL' => LuminousTokenPresets::$C_COMMENT_SL,
'COMMENT_ML' => LuminousTokenPresets::$C_COMMENT_ML,
'STRING_S' => LuminousTokenPresets::$SINGLE_STR,
'STRING_D' => LuminousTokenPresets::$DOUBLE_STR,
// TODO check var naming, is $1 a legal variable?
'VARIABLE' => '%\$[\-a-z_0-9]+ | \#\{\$[\-a-z_0-9]+\} %x',
'AT_IDENTIFIER' => '%@[a-zA-Z0-9]+%',
// This is generic - it may be a selector fragment, a rule, or
// even a hex colour.
'GENERIC_IDENTIFIER' => '@
\\#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?
|
[0-9]+(\.[0-9]+)?(\w+|%|in|cm|mm|em|ex|pt|pc|px|s)?
|
-?[a-zA-Z_\-0-9]+[a-zA-Z_\-0-9]*
|&
@x',
'IMPORTANT' => '/!important/',
'L_BRACE' => '/\{/',
'R_BRACE' => '/\}/',
'L_SQ_BRACKET' => '/\[/',
'R_SQ_BRACKET' => '/\]/',
'L_BRACKET' => '/\(/',
'R_BRACKET' => '/\)/',
'DOUBLE_COLON' => '/::/',
'COLON' => '/:/',
'SEMICOLON' => '/;/',
'DOT' => '/\./',
'HASH' => '/#/',
'COMMA' => '/,/',
'OTHER_OPERATOR' => '@[+\-*/%&>=!]@',
'WHITESPACE' => '/\s+/'
);
}
public function main() {
while (!$this->eos()) {
$m = null;
foreach($this->regexen as $token=>$pattern) {
if ( ($m = $this->scan($pattern)) !== null) {
$this->record($m, $token);
break;
}
}
if ($m === null) {
$this->record($this->get(), null);
}
}
$parser = new LuminousSASSParser();
$parser->tokens = $this->tokens;
$parser->parse();
$this->tokens = $parser->tokens;
}
}
/**
* The parsing class
*/
class LuminousSASSParser {
public $tokens;
public $index;
public $stack;
static $delete_token = 'delete';
/**
* Returns true if the next token is the given token name
* optionally skipping whitespace
*/
function next_is($token_name, $ignore_whitespace = false) {
$i = $this->index+1;
$len = count($this->tokens);
while($i<$len) {
$tok = $this->tokens[$i][0];
if ($ignore_whitespace && $tok === 'WHITESPACE') {
$i++;
}
else {
return $tok === $token_name;
}
}
return false;
}
/**
* Returns the index of the next match of the sequence of tokens
* given, optionally ignoring ertain tokens
*/
function next_sequence($sequence, $ignore=array()) {
$i = $this->index+1;
$len = count($this->tokens);
$seq_len = count($sequence);
$seq = 0;
$seq_start = 0;
while ($i<$len) {
$tok = $this->tokens[$i][0];
if ($tok === $sequence[$seq]) {
if ($seq === 0) $seq_start = $i;
$seq++;
$i++;
if ($seq === $seq_len) {
return $seq_start;
}
} else {
if (in_array($tok, $ignore)) {}
else {
$seq = 0;
}
$i++;
}
}
return $len;
}
/**
* Returns the first token which occurs out of the set of given tokens
*/
function next_of($token_names) {
$i = $this->index+1;
$len = count($this->tokens);
while ($i<$len) {
$tok = $this->tokens[$i][0];
if (in_array($tok, $token_names)) {
return $tok;
}
$i++;
}
return null;
}
/**
* Returns the index of the next token with the given token name
*/
function next_of_type($token_name) {
$i = $this->index+1;
$len = count($this->tokens);
while($i<$len) {
$tok = $this->tokens[$i][0];
if ($tok === $token_name) {
return $i;
}
$i++;
}
return $len;
}
private function _parse_identifier($token) {
$val = $token[1];
$c = isset($val[0])? $val[0] : '';
if (ctype_digit($c) || $c === '#') {
$token[0] = 'NUMERIC';
}
}
/**
* Parses a selector rule
*/
private function _parse_rule() {
$new_token = $this->tokens[$this->index];
$set = false;
if ($this->index > 0) {
$prev_token = &$this->tokens[$this->index-1];
$prev_token_type = &$prev_token[0];
$prev_token_text = &$prev_token[1];
$concat = false;
$map = array(
'DOT' => 'CLASS_SELECTOR',
'HASH' => 'ID_SELECTOR',
'COLON' => 'PSEUDO_SELECTOR',
'DOUBLE_COLON' => 'PSEUDO_SELECTOR'
);
if (isset($map[$prev_token_type])) {
// mark the prev token for deletion and concat into one.
$new_token[0] = $map[$prev_token_type];
$prev_token_type = self::$delete_token;
$new_token[1] = $prev_token_text . $new_token[1];
$set = true;
}
}
if (!$set) {
// must be an element
$new_token[0] = 'ELEMENT_SELECTOR';
}
$this->tokens[$this->index] = $new_token;
}
/**
* Cleans up the token stream by deleting any tokens marked for
* deletion, and makes sure the array is continuous afterwards.
*/
private function _cleanup() {
foreach($this->tokens as $i=>$t) {
if ($t[0] === self::$delete_token) {
unset($this->tokens[$i]);
}
}
$this->tokens = array_values($this->tokens);
}
/**
* Main parsing function
*/
public function parse() {
$new_tokens = array();
$len = count($this->tokens);
$this->stack = array();
$prop_value = 'PROPERTY';
$pushes = array(
'L_BRACKET' => 'bracket',
'L_BRACE' => 'brace',
'AT_IDENTIFIER' => 'at',
'L_SQ_BRACKET' => 'square'
);
$pops = array(
'R_BRACKET' => 'bracket',
'R_BRACE' => 'brace',
'R_SQ_BRACKET' => 'square'
);
$this->index = 0;
while($this->index < $len) {
$token = &$this->tokens[$this->index];
$stack_size = count($this->stack);
$state = !$stack_size? null : $this->stack[$stack_size-1];
$tok_name = &$token[0];
$in_brace = in_array('brace', $this->stack);
$in_bracket = in_array('bracket', $this->stack);
$in_sq = in_array('square', $this->stack);
$in_at = in_array('at', $this->stack);
if ($tok_name === self::$delete_token) continue;
if ($tok_name === 'L_BRACE') {
if ($state === 'at') {
array_pop($this->stack);
}
$this->stack[] = $pushes[$tok_name];
$prop_value = 'PROPERTY';
}
elseif (isset($pushes[$tok_name])) {
$this->stack[] = $pushes[$tok_name];
} else if (isset($pops[$tok_name]) && $state === $pops[$tok_name]) {
array_pop($this->stack);
}
elseif (!$in_bracket && $tok_name === 'COLON') {
$prop_value = 'VALUE';
}
elseif ($tok_name === 'SEMICOLON') {
$prop_value = 'PROPERTY';
if ($state === 'at') array_pop($this->stack);
}
elseif ($tok_name === 'GENERIC_IDENTIFIER') {
// this is where the fun starts.
// we have to figure out exactly what this is
// if we can look ahead and find a '{' before we find a
// ';', then this is part of a selector.
// Otherwise it's part of a property/value pair.
// the exception is when we have something like:
// font : { family : sans-serif; }
// then we need to check for ':{'
if ($in_sq) {
$token[0] = 'ATTR_SELECTOR';
}
else if ($in_bracket) {
$this->_parse_identifier($token);
}
elseif(!$in_at) {
$semi = $this->next_of_type('SEMICOLON');
$colon_brace = $this->next_sequence(array('COLON', 'L_BRACE'),
array('WHITESPACE'));
$brace = $this->next_of_type('L_BRACE');
$rule_terminator = min($semi, $colon_brace);
if ($brace < $rule_terminator) {
$this->_parse_rule();
$prop_value = 'PROPERTY';
} else {
$this->tokens[$this->index][0] = $prop_value;
if ($prop_value === 'VALUE') {
$this->_parse_identifier($token);
}
}
}
}
$this->index++;
}
$this->_cleanup();
}
}

View file

@ -0,0 +1,62 @@
<?php
class LuminousSQLScanner extends LuminousSimpleScanner {
public function init() {
$this->case_sensitive = false;
// $this->remove_stream_filter('oo-syntax');
$this->remove_filter('comment-to-doc');
$this->remove_filter('constant');
include(dirname(__FILE__) . '/include/sql.php');
$this->add_identifier_mapping('KEYWORD', $keywords);
$this->add_identifier_mapping('TYPE', $types);
$this->add_identifier_mapping('VALUE', $values);
$this->add_identifier_mapping('OPERATOR', $operators);
$this->add_identifier_mapping('FUNCTION', $functions);
$this->add_pattern('IDENT', '/[a-zA-Z_]+\w*/');
$this->add_pattern('COMMENT', LuminousTokenPresets::$C_COMMENT_ML);
// # is for MySQL.
$this->add_pattern('COMMENT', '/(?:\#|--).*/');
$this->add_pattern('STRING', LuminousTokenPresets::$SQL_SINGLE_STR_BSLASH);
$this->add_pattern('STRING', LuminousTokenPresets::$DOUBLE_STR);
$this->add_pattern('STRING', '/ ` (?> [^\\\\`]+ | \\\\. )* (?: `|$)/x');
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_HEX);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_REAL);
$this->add_pattern('OPERATOR', '/[¬!%^&*\\-=+~:<>\\|\\/]+/');
$this->add_pattern('KEYWORD', '/\\?/');
}
public static function guess_language($src, $info) {
// we have to be careful not to assign too much weighting to
// generic SQL keywords, which will often appear in other languages
// when those languages are executing SQL statements
//
// All in all, SQL is pretty hard to recognise because generally speaking,
// an SQL dump will probably contain only a tiny fraction of SQL keywords
// with the majority of the text just being data.
$p = 0.0;
// if we're lucky, the top line will be a comment containing the phrase
// 'SQL' or 'dump'
if (strpos($info['trimmed'], '--') === 0 && isset($info['lines'][0])
&& (
stripos($info['lines'][0], 'sql') !== false)
|| stripos($info['lines'][0], 'dump' !== false)
)
$p = 0.5;
foreach(array('SELECT', 'CREATE TABLE', 'INSERT INTO', 'DROP TABLE',
'INNER JOIN', 'OUTER JOIN') as $str)
{
if (strpos($src, $str) !== false) $p += 0.01;
}
// single line comments --
if (preg_match_all('/^--/m', $src, $m) > 5)
$p += 0.05;
if (preg_match('/VARCHAR\(\d+\)/', $src)) $p += 0.05;
return $p;
}
}

View file

@ -0,0 +1,78 @@
<?php
/*
* VB.NET
*
* Language spec:
* http://msdn.microsoft.com/en-us/library/aa712050(v=vs.71).aspx
*
* TODO: IIRC vb can be embedded in asp pages like php or ruby on rails,
* and XML literals: these are a little bit confusing, something
* like "<xyz>.something" appears to be a valid XML fragment (i.e. the <xyz>
* is a complete fragment), but at other times, the fragment would run until
* the root tag is popped. Need to find a proper description of the grammar
* to figure it out
*/
class LuminousVBScanner extends LuminousSimpleScanner {
public $case_sensitive = false;
public function init() {
$this->add_pattern('PREPROCESSOR', "/^[\t ]*#.*/m");
$this->add_pattern('COMMENT', "/'.*/");
$this->add_pattern('COMMENT', '/\\bREM\\b.*/i');
// float
$this->add_pattern('NUMERIC', '/ (?<!\d)
\d+\.\d+ (?: e[+\\-]?\d+)?
|\.\d+ (?: e[+\\-]?\d+)?
| \d+ e[+\\-]?\d+
/xi');
// int
$this->add_pattern('NUMERIC', '/ (?:
&H[0-9a-f]+
| &O[0-7]+
| (?<!\d)\d+
) [SIL]*/ix');
$this->add_pattern('CHARACTER', '/"(?:""|.)"c/i');
$this->add_pattern('STRING', '/" (?> [^"]+ | "" )* ($|")/x');
// in theory we should also match unicode quote chars
// in reality, well, I read the php docs and I have no idea if it's
// even possible.
// The chars are:
// http://www.fileformat.info/info/unicode/char/201c/index.htm
// and
// http://www.fileformat.info/info/unicode/char/201d/index.htm
// date literals, this isn't as discriminating as the grammar specifies.
$this->add_pattern('VALUE', "/#[ \t][^#\n]*[ \t]#/");
$this->add_pattern('OPERATOR', '/[&*+\\-\\/\\\\^<=>,\\.]+/');
// http://msdn.microsoft.com/en-us/library/aa711645(v=VS.71).aspx
// XXX: it warns about ! being ambiguous but I don't see how it can be
// ambiguous if we use this regex?
$this->add_pattern('IDENT', '/[a-z_]\w*[%&@!#$]?/i');
// we'll borrow C#'s list of types (ie modules, classes, etc)
include(dirname(__FILE__) . '/include/vb.php');
include(dirname(__FILE__) . '/include/csharp_list.php');
$this->add_identifier_mapping('VALUE', $luminous_vb_values);
$this->add_identifier_mapping('OPERATOR', $luminous_vb_operators);
$this->add_identifier_mapping('TYPE', $luminous_vb_types);
$this->add_identifier_mapping('KEYWORD', $luminous_vb_keywords);
$this->add_identifier_mapping('TYPE', $luminous_csharp_type_list);
}
public static function guess_language($src, $info) {
$p = 0.0;
if (preg_match('/^Imports\s+System/i', $src)) $p += 0.1;
if (preg_match('/Dim\s+\w+\s+As\s+/i', $src)) $p += 0.2;
if (preg_match('/(Public|Private|Protected)\s+Sub\s+/i', $src)) $p += 0.1;
return $p;
}
}

View file

@ -0,0 +1,63 @@
<?php
// I can't find some formal definition of vimscript's grammar.
// I'm pretty sure it's more complex than this, but, who knows.
require_once(dirname(__FILE__) . '/include/vim_list.php');
class LuminousVimScriptScanner extends LuminousSimpleScanner {
public function string_override() {
$comment = $this->bol();
$this->skip_whitespace();
assert($this->peek() === '"');
if ($comment) {
$this->record($this->scan("/.*/"), 'COMMENT');
} else {
if ($this->scan("/ \" (?> [^\n\"\\\\]+ | \\\\. )*$ /mx")) {
$this->record($this->match(), 'COMMENT');
}
else {
$m = $this->scan(LuminousTokenPresets::$DOUBLE_STR);
assert($m !== null);
$this->record($m, 'STRING');
}
}
}
static function comment_filter($token) {
$token = LuminousUtils::escape_token($token);
$str = &$token[1];
// It pays to run the strpos checks first.
if (strpos(substr($str, 1), '"') !== false)
$str = preg_replace('/(?<!^)"(?>[^"]*)"/', "<STRING>$0</STRING>", $str);
if (strpos($str, ':') !== false)
$str = preg_replace('/(?<=^")((?>\W*))((?>[A-Z]\w+(?>(?>\s+\w+)*)))(:\s*)(.*)/',
'$1<DOCTAG>$2</DOCTAG>$3<DOCSTR>$4</DOCSTR>', $str);
return $token;
}
function init() {
$this->add_pattern('COMMENT_STRING', "/[\t ]*\"/");
$this->add_pattern('STRING', "/'(?>[^\n\\\\']+ | \\\\. )*'/x");
$this->add_pattern('NUMERIC','/\#[a-f0-9]+/i');
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_HEX);
$this->add_pattern('NUMERIC', LuminousTokenPresets::$NUM_REAL);
$this->add_pattern('IDENT', '/[a-z_]\w*/i');
$this->add_pattern('OPERATOR', '@[~¬!%^&*\-=+;:,<.>/?\|]+@');
$this->add_identifier_mapping('FUNCTION',
$GLOBALS['luminous_vim_functions']);
$this->add_identifier_mapping('KEYWORD',
$GLOBALS['luminous_vim_keywords']);
$this->remove_stream_filter('oo-syntax');
$this->remove_filter('comment-to-doc');
$this->add_filter('comment', 'COMMENT', array($this, 'comment_filter'));
$this->overrides = array('COMMENT_STRING' => array($this, 'string_override'));
}
}

View file

@ -0,0 +1,31 @@
<?php
/*
* HTML is the 'root' scanner, we just override a couple of config settings
* here, to prevent it from looking for CSS or JS.
*/
class LuminousXMLScanner extends LuminousHTMLScanner {
public $scripts = false;
public $embedded_server = false;
public static function guess_language($src, $info) {
if (strpos(ltrim($src), '<?xml') === 0) return 1.0;
// don't catch HTML doctypes
if (strpos($src, '<!DOCTYPE') !== false) return 0;
$p = 0;
// simple tag
$lines = preg_match_all('/$/m',
preg_replace('/^\s+/m', '', $src), $m);
// avg 1 tag every 4 lines
if (preg_match_all('%<[!?/]?[a-zA-Z_:\\-]%', $src, $m)
> $lines/4) $p += 0.15;
// self closing tag
if (strpos($src, '/>') !== false) $p += 0.05;
// tag with attr
if (preg_match('/<[a-zA-Z_]\w*\s+[a-zA-Z_]\w+\s*=["\']/', $src))
$p += 0.1;
return $p;
}
}

View file

@ -0,0 +1,17 @@
<?php
/*
* If we're running in a web environment, this is simply an include
* file which includes everything necessary to use Luminous.
*
* If we're running in CLI-mode then this handles the CLI interface.
*
*/
require_once(dirname(__FILE__) . '/src/luminous.php');
if (PHP_SAPI === 'cli') {
// cli mode
if (isset($argv[0]) && $argv[0] === basename(__FILE__))
require(dirname(__FILE__) . '/src/cli.php');
}

View file

@ -0,0 +1 @@
Deny from all

View file

@ -0,0 +1,222 @@
<?php
///@cond ALL
/* command line interface */
class LuminousCLI {
private $options = array(
'input-file' => null,
'output-file' => null,
'lang' => null,
'format' => 'html-full',
'height' => 0,
'theme' => 'geonyx',
'code' => null,
'line-numbers' => true,
);
private $cmd_option_map = array(
'-i' => 'input-file',
'-o' => 'output-file',
'-l' => 'lang',
'-f' => 'format',
'-h' => 'height',
'-t' => 'theme',
);
static function print_help() {
echo <<<EOF
Usage:
php luminous.php [OPTIONS] [SOURCE_CODE]
SOURCE_CODE may be omitted if you specify -i. Use '-' to read code from stdin.
Options:
-f <format> Output format. This can be:
'html' - An HTML snippet (div). CSS is not included
on the page, the style-sheets must be included by
hand.
'html-full' - A full HTML page. CSS is embedded.
'latex' - A LaTeX document.
Default: 'html-full'
-h <height> Constrains the height of the widget with 'html'
formatter. Has no effect on other formatters.
Default: 0
-i <filename> Input file. If this is omitted, SOURCE_CODE is used.
-l <language> Language code. If this is omitted, the language is
guessed.
-o <filename> Output file to write. If this is omitted, stdout is
used.
-t <theme> Theme to use. See --list-themes for valid themes
--no-numbers Disables line numbering
--list-codes Lists valid language codes and exits
--list-themes Lists valid themes and exits
--help Display this text and exit
--version Display version number and exit
EOF;
}
function error($string) {
echo "Error: $string
see --help for help
";
exit(1);
}
function set_lookahead($option, $i) {
global $argv;
if (isset($argv[$i+1]))
$this->options[$this->cmd_option_map[$option]] = $argv[$i+1];
else self::error('Missing option for ' . $option);
}
function list_codes() {
foreach(luminous::scanners() as $name=>$codes) {
echo sprintf("%s: %s\n", $name, join(', ', $codes));
}
exit(0);
}
function list_themes() {
echo preg_replace('/\.css$/m', '', join("\n", luminous::themes()) . "\n");
exit(0);
}
function parse_args() {
global $argv, $argc;
for($i=1; $i<$argc; $i++) {
$a = $argv[$i];
if (isset($this->cmd_option_map[$a])) {
$this->set_lookahead($a, $i++);
}
elseif ($a === '--list-codes') {
self::list_codes();
}
elseif ($a === '--list-themes') {
self::list_themes();
}
elseif ($a === '--help') {
self::print_help();
exit(0);
}
elseif($a === '--version') {
echo LUMINOUS_VERSION;
echo "\n";
exit(0);
}
elseif ($a === '--no-numbers') {
$this->options['line-numbers'] = false;
}
else {
if ($this->options['code'] !== null) {
self::error('Unknown option: ' . $a);
} else {
$this->options['code'] = $a;
}
}
}
}
function highlight() {
$this->parse_args();
// figure out the code
// error cases are:
// no input file or source code,
if ($this->options['code'] === null
&& $this->options['input-file'] === null) {
$this->error('No input file or source code specified');
}
// or both input file and source code
elseif ($this->options['code'] !== null
&& $this->options['input-file'] !== null) {
$this->error('Input file (-i) and source code specified. You probably '
. 'didn\'t mean this');
}
// is there an input file? use that.
if ($this->options['input-file'] !== null) {
$c = @file_get_contents($this->options['input-file']);
if ($c === false) {
$this->error('Could not read from ' . $this->options['input-file']);
}
else {
$this->options['code'] = $c;
}
}
// else we're expecting code to have been given on the command line,
// but it might be '-' which means read stdin
elseif ($this->options['code'] === '-') {
$code = '';
while (($line = fgets(STDIN)) !== false)
$code .= $line;
$this->options['code'] = $code;
}
// set the formatter
luminous::set('format', $this->options['format']);
// lame check that the formatter is okay
try { luminous::formatter(); }
catch(Exception $e) {
$this->error('Unknown formatter ' . $this->options['format']);
}
// set the theme
$valid_themes = luminous::themes();
$theme = $this->options['theme'];
if (!preg_match('/\.css$/', $theme)) $theme .= '.css';
if (!luminous::theme_exists($theme)) {
$this->error('No such theme: ' . $theme);
} else {
luminous::set('theme', $theme);
}
// set the language
if ($this->options['lang'] === null) {
// guessing
$this->options['lang'] = luminous::guess_language($this->options['code']);
}
// user provided language
$scanners = luminous::scanners();
$valid_scanner = false;
foreach($scanners as $lang=>$codes) {
if (in_array($this->options['lang'], $codes)) {
$valid_scanner = true;
break;
}
}
if (!$valid_scanner) $this->error('No such language: '
. $this->options['lang']);
// other options
luminous::set('max-height', $this->options['height']);
luminous::set('line-numbers', $this->options['line-numbers']);
$h = luminous::highlight($this->options['lang'], $this->options['code']);
if ($this->options['output-file'] !== null) {
$r = @file_put_contents($this->options['output-file'], $h, LOCK_EX);
if ($r === false)
$this->error('Could not write to ' . $this->options['output-file']);
} else {
echo $h;
}
exit(0);
}
}
function main() {
$luminous_cli = new LuminousCLI();
$luminous_cli->highlight();
}
main();
///@endcond

View file

@ -0,0 +1,296 @@
<?php
/**
* @cond CORE
* @brief A collection of useful common filters.
*
* Filters are either stream filters or individual filters.
* Stream filters operate on the entire token stream, and return the new
* token stream. Individual filters operate on individual tokens (bound by type),
* and return the new token. Any publicly available member here is one of those,
* therefore the return and param documents are omitted.
*
*/
// Poor man's namespace.
class LuminousFilters {
/**
* @brief Gets the expected number of arguments to a doc-comment command/tag
* @param $command the name of the command
* @returns The expected number of arguments for a command, this is either
* 0 or 1 at the moment
* @internal
*/
private static function doxygen_arg_length($command) {
switch(strtolower($command)) {
case "addtogroup":
case "category":
case "class":
case "cond":
case "def":
case "defgroup":
case "dir":
case "elseif":
case "enum":
case "exception":
case "example":
case "extends":
case "if":
case "ifnot":
case "file":
case "headerfile":
case "implements":
case "ingroup":
case "interface":
case "memberof":
case "namespace":
case "package":
case "page":
case "par":
case "param":
case "relates":
case "relatesalso":
case "retval":
case 'see':
case 'since':
case "tparam":
case "throw":
case "throws":
case "weakgroup":
case "xrefitem":
return 1;
default: return 0;
}
}
/**
* @brief callback to doxygenize
* Highlights Doxygen-esque doc-comment syntax.
* This is a callback to doxygenize().
* @return the highlighted string
* @internal
*/
private static function doxygenize_cb($matches) {
$lead = $matches[1];
$tag_char = $matches[2];
$tag = $matches[3];
$line = "";
if (isset($matches[4]))
$line = $matches[4];
$len = -1;
// JSDoc-like
$l_ = ltrim($line);
if (isset($l_[0]) && $l_[0] === '{') {
$line = preg_replace('/({[^}]*})/', "<DOCPROPERTY>$1</DOCPROPERTY>", $line);
return "$lead<DOCTAG>$tag_char$tag</DOCTAG>$line";
}
else
$len = self::doxygen_arg_length($tag);
if($len === 0)
return "$lead<DOCTAG>$tag_char$tag</DOCTAG>$line";
else {
$l = explode(' ', $line);
$start = "$lead<DOCTAG>$tag_char$tag</DOCTAG><DOCPROPERTY>";
$j = 0;
$c = count($l);
for($i=0; $j<$len && $i<$c; $i++)
{
$s = $l[$i];
$start .= $s . ' ';
unset($l[$i]);
if (trim($s) !== '')
$j++;
}
$start = preg_replace('/ $/', '', $start);
$start .= "</DOCPROPERTY>";
$l = array_values($l);
if (!empty($l)) $start .= ' ';
$start .= implode(' ', $l);
return $start;
}
}
/**
* @brief Highlights doc-comment tags inside a comment block.
*
* @see generic_doc_comment
* @internal
*/
static function doxygenize($token) {
$token = LuminousUtils::escape_token($token);
$token[1] = preg_replace_callback("/(^(?>[\/\*#\s\{]*))([\@\\\])([\\w]*)(\}|[ \t]+.*?)?$/m",
array('LuminousFilters', 'doxygenize_cb'), $token[1]);
return $token;
}
/**
* @brief Generic filter to highlight JavaDoc, PHPDoc, Doxygen, JSdoc, and similar doc comment syntax.
*
* A cursory check will be performed to try to validate that the token
* really is a doc-comment, it does this by checking for common formats.
*
* If the check is successful, the token will be switched to type
* 'DOCCOMMENT' and its doc-tags will be highlighted
*
* This is a wrapper around doxygenize(). If the checks are not necessary,
* or incorrect for your situation, you may instead choose to use
* doxygenize() directly.
*/
static function generic_doc_comment($token) {
// checks if a comment is in the form:
// xyyz where x may = y but y != z.
// This matches, say, /** comment but does not match /********/
// same with /// comment but not ///////////////
$s = $token[1];
if (isset($s[3])
&& ($s[2] === $s[1] || $s[2] === '!')
&& !ctype_space($s[0])
&& !ctype_space($s[1])
&& $s[3] !== $s[2]
)
{
$token[0] = 'DOCCOMMENT';
$token = self::doxygenize($token);
}
return $token;
}
/**
* @brief Highlights comment notes
* Highlights keywords in comments, i.e. "NOTE", "XXX", "FIXME", "TODO",
* "HACK", "BUG"
*/
static function comment_note($token) {
$token = LuminousUtils::escape_token($token);
$token[1] = preg_replace('/\\b(?:NOTE|XXX|FIXME|TODO|HACK|BUG):?/',
'<COMMENT_NOTE>$0</COMMENT_NOTE>', $token[1]);
return $token;
}
/**
* @brief Highlights generic escape sequences in strings
* Highlights escape sequences in strings. There is no checking on which
* sequences are legal, this is simply a generic function which checks for
* \\u... unicode, \\d... octal, \\x... hex and finally just any character
* following a backslash.
*/
static function string($token) {
if (strpos($token[1], '\\') === false) return $token;
$token = LuminousUtils::escape_token($token);
$token[1] = preg_replace('/
\\\\
(?:
(?:u[a-f0-9]{4,8}) # unicode
|\d{1,3} # octal
|x[a-fA-F0-9]{2} # hex
|. # whatever
)
/xi', '<ESC>$0</ESC>', $token[1]);
return $token;
}
/**
* @brief Tries to highlight PCRE style regular expression syntax
*/
static function pcre($token, $delimited=true) {
$token = self::string($token);
$token = LuminousUtils::escape_token($token);
$str = &$token[1];
$flags = array();
if ($delimited) {
$str = preg_replace('/^[^[:alnum:]<>\s]/', '<DELIMITER>$0</DELIMITER>', $str);
if (preg_match("/[[:alpha:]]+$/", $str, $matches)){
$m = $matches[0];
$flags = str_split($m);
$str = preg_replace("/((?<!\A)[^[:alnum:]\s<>])([[:alpha:]]+)$/",
"<DELIMITER>$1</DELIMITER><KEYWORD>$2</KEYWORD>", $str);
} else
$str = preg_replace('/[^[:alnum:]<>]$/', '<DELIMITER>$0</DELIMITER>', $str);
}
$str = preg_replace("/((?<!\\\)[\*\+\.|])|((?<![\(\\\])\?)/",
"<REGEX_OPERATOR>$0</REGEX_OPERATOR>", $str);
$str = preg_replace("/(?<=\()\?(?:(?:[a-zA-Z:!|=])|(?:(?:&lt;)[=!]))/",
"<REGEX_SUBPATTERN>$0</REGEX_SUBPATTERN>", $str);
$str = preg_replace("/(?<!\\\)[\(\)]/",
"<REGEX_SUBPATTERN_MARKER>$0</REGEX_SUBPATTERN_MARKER>", $str);
$str = preg_replace("/(?<!\\\)[\[\]]/",
"<REGEX_CLASS_MARKER>$0</REGEX_CLASS_MARKER>", $str);
$str = preg_replace("/(?<!\\\)
\{
(
((?>\d+)(,(?>\d+)?)?)
|
(,(?>\d+))
)
\}/x", "<REGEX_REPEAT_MARKER>$0</REGEX_REPEAT_MARKER>", $str);
// extended regex: # signifies a comment
if (in_array('x', $flags))
$str = preg_replace('/(?<!\\\)#.*$/m', '<COMMENT>$0</COMMENT>',
$str);
return $token;
}
/**
* @brief Translates any token type of an uppercase/numeric IDENT to 'CONSTANT'
*/
static function upper_to_constant($token) {
// check for this because it may have been mapped to a function or something
if ($token[0] === 'IDENT' && preg_match('/^[A-Z_][A-Z_0-9]{3,}$/', $token[1]))
$token[0] = 'CONSTANT';
return $token;
}
/**
* @brief Translates anything of type 'IDENT' to the null type
*/
static function clean_ident($token) {
if ($token[0] === 'IDENT') $token[0] = null;
return $token;
}
/**
* @brief Attempts to apply OO syntax highlighting
*
* Tries to apply generic OO syntax highlighting. Any identifer immediately
* preceding a '.', '::' or '->' token is mapped to an 'OO'.
* Any identifer token immediatel following any of those tokens is mapped to
* an 'OBJ'.
* This is a stream filter.
*/
static function oo_stream_filter($tokens) {
$c = count($tokens);
for($i=0; $i<$c; $i++) {
if ($tokens[$i][0] !== 'IDENT') continue;
if ($i > 0) {
$s = $tokens[$i-1][1];
if ($s === '.' || $s === '->' || $s === '::') {
$tokens[$i][0] = 'OO';
$i++;
continue;
}
}
if ($i < $c-1) {
$s = $tokens[$i+1][1];
if ($s === '.' || $s === '->' || $s === '::') {
$tokens[$i][0] = 'OBJ';
$i++;
}
}
}
return $tokens;
}
}
/// @endcond
// end CORE

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,84 @@
<?php
/**
* @cond CORE
* @brief A basic preg_match wrapper which caches its results
*
* @note This class is used by Scanner and should not need to be used by
* anything else.
*
* A class encapsulating the process of searching for a substring efficiently
* it handles caching of results.
*
* @warning One instance should be used only incrementally along a string.
* i.e. do not call it with index = 5 then index = 1.
*
* @internal
*
*/
class LuminousStringSearch
{
/// A copy of the string to operate on.
private $string;
/**
* The cache is stored as a map of pattern => result,
* the result is an array of:
* (0=>index, 1=>match_groups), OR, it is false if there are no more results
* left in the string.
*/
private $cache = array();
public function __construct($str) {
$this->string = $str;
}
/**
* @brief Performs a search for the given pattern past the given index.
* @param $search the pattern to search for
* @param $index the minimum string index (offset) of a result
* @param $matches a reference to the return location of the match groups
* @return the index or false if no match is found.
*/
public function
match($search, $index, &$matches) {
$r = false; // return value
if (isset($this->cache[$search])) {
$a = $this->cache[$search];
if ($a === false) return false; // no more results
$r = $a[0];
$matches = $a[1];
assert($matches !== null);
if ($r >= $index) // cache is good!
return $r;
}
// cache not set, or out of date, we have to perform the match
if (!($ret = preg_match($search, $this->string, $matches_,
PREG_OFFSET_CAPTURE, $index))) {
if ($ret === false && LUMINOUS_DEBUG) {
throw new Exception('preg_match returned false for pattern: "'
. $search . '", with code: ' . LuminousUtils::pcre_error_decode(
preg_last_error()) . " with string length " . strlen($this->string)
. " and offset " . $index
);
}
$this->cache[$search] = false;
return false;
}
$r = $matches_[0][1];
// strip the offsets from the match_groups
foreach($matches_ as $i=>&$v)
$v = $v[0];
$this->cache[$search] = array($r, $matches_);
$matches = $matches_;
return $r;
}
}
/// @endcond

View file

@ -0,0 +1,49 @@
<?php
/**
* @cond CORE
* @brief A set of pre-defined patterns to match various common tokens
*/
abstract class LuminousTokenPresets {
/// multi-line double quoted string using backslash escapes
static $DOUBLE_STR = '/" (?> [^"\\\\]+ | \\\\.)* (?:"|$)/xs';
/// single line double quoted string using backslash escapes
static $DOUBLE_STR_SL = "/\"(?> [^\\\\\"\n]+ | \\\\.)*(?:\$|\")/xms";
/// multi-line single quote string using backslash escapes
static $SINGLE_STR = "/' (?> [^'\\\\]+ | \\\\.)* (?:'|\$)/xs";
/// single line single quoted string using backslash escapes
static $SINGLE_STR_SL = "/'(?> [^\\\\'\n]+ | \\\\.)*(?:\$|')/xms";
/// Single quoted c-style character
static $CHAR = "(?: \\\\(?: x[A-F0-9]{1,2}| . ) | . ) (?: '|\$)/ixm";
/// hexadecimal literal
static $NUM_HEX = '/0[Xx][a-fA-F0-9]+/';
/// Real number, i.e. an integer or a float, optionally with an exponent
static $NUM_REAL = '/
(?: \d+ (?: \.\d+ )? | \.?\d+) # int, fraction or significand
(?:e[+-]?\d+)? # exponent
/ix';
/// Single line c++ style comment
static $C_COMMENT_SL = '% // .* %x';
/// Multiline C style comment
static $C_COMMENT_ML = '% / \* (?> [^\\*]+ | \\*(?!/) )* (?: \\*/ | $) %sx';
/// Perl/Python/Ruby style hash-comment (single line)
static $PERL_COMMENT = '/#.*/';
/// SQL style single quoted string using '' to escape
static $SQL_SINGLE_STR = "/ ' (?> [^']+ | '' )* (?: '|\$)/x";
/// SQL style single quoted string using '' or \' to escape
static $SQL_SINGLE_STR_BSLASH = "/ ' (?> [^'\\\\]+ | '' | \\\\. )* (?: '|\$)/x";
}
/// @endcond

View file

@ -0,0 +1,118 @@
<?php
/**
* @cond CORE
* @brief A set of utility functions for scanners
*/
class LuminousUtils {
/**
* @brief Tries to balance a delimiter
*
* Tries to 'balance' a single character delimiter, i.e:
* '(' is mapped to ')'
* '{' is mapped to '}',
* '[' is mapped to ']',
* '<' is mapped to '>'
* Any other character is mapped to itself.
*
* @param $delimiter the left/opening delimiter to try to balance
* @return The corresponding close delimiter character, or the input
* character.
*/
static function balance_delimiter($delimiter) {
switch($delimiter) {
case '(' : return ')';
case '{' : return '}';
case '[' : return ']';
case '<' : return '>';
default: return $delimiter;
}
}
/**
* @brief Escapes a string suitable for use in XML
*
* Escapes a string according to the Luminous internal escaping format
* (this is currently htmlspecialchars with ENT_NOQUOTES.)
* @param $string the string to escape
* @return the escaped string
*/
static function escape_string($string) {
return htmlspecialchars($string, ENT_NOQUOTES);
}
/**
* @brief Escapes a token so its string is suitable for use in XML
*
* Escapes a token. If the token is already escaped, nothing changes.
* If the token is not escaped, the escaped flag is set (index 2) and the
* token text (index 1) is escaped according to the internal escaping format
* @param $token the token to escape
* @return the escaped token
*/
static function escape_token($token) {
$esc = &$token[2];
if (!$esc) {
$str = &$token[1];
$str = htmlspecialchars($str, ENT_NOQUOTES);
$esc = true;
}
return $token;
}
/**
* @brief Wraps a block of text in an XML tag
*
* Tags a block of text. The block is assumed to have been escaped correctly
* with LuminousUtils::escape_string.
* @param $type the type to tag the string as, this is the token name
* @param $block the block of text
* @param $split_multiline if this is set to true, the tags are closed at
* the end of each line and re-opened again on the next line. This is
* useful for output formats like HTML, where spanning multiple lines
* could break the markup
* @return The tagged block of text. This resembles an XML fragment.
*/
static function tag_block($type, $block, $split_multiline=true) {
if ($type === null) return $block;
$open = '<' . $type . '>';
$close = '</' . $type . '>';
if ($split_multiline)
return $open . str_replace("\n", $close . "\n" . $open, $block) .
$close;
else
return $open . $block . $close;
}
/**
* @brief Decodes PCRE error codes to human readable strings
*
* Decodes a PCRE error code, which was returned by preg_last_error(), to
* something readable
* @param $errcode the error code
* @return the error description, as string. This is currently the same
* as the constant name, so the constant PREG_NO_ERROR is mapped to the
* string 'PREG_NO_ERROR'
*/
static function pcre_error_decode($errcode) {
switch ($errcode) {
case PREG_NO_ERROR:
return 'PREG_NO_ERROR';
case PREG_INTERNAL_ERROR:
return 'PREG_INTERNAL_ERROR';
case PREG_BACKTRACK_LIMIT_ERROR:
return 'PREG_BACKTRACK_LIMIT_ERROR';
case PREG_RECURSION_LIMIT_ERROR:
return 'PREG_RECURSION_LIMIT_ERROR';
case PREG_BAD_UTF8_ERROR:
return 'PREG_BAD_UTF8_ERROR';
case PREG_BAD_UTF8_OFFSET_ERROR:
return 'PREG_BAD_UTF8_OFFSET_ERROR';
default:
return "Unknown error code `$errcode'";
}
}
}
/// @endcond
// ends CORE

View file

@ -0,0 +1,3 @@
<?php
define('LUMINOUS_DEBUG', false);

View file

@ -0,0 +1,20 @@
<?php
// A few things to make Doxygen a little nicer. No code here.
/**
* @mainpage
*
* This is the API documentation for Luminous, a PHP syntax highlighter.
* This is mostly for development purposes, and not a lot of use to users.
*
* However, users may be interested in the \link luminous user's API \endlink
* and the LuminousOptions class
*
*
* Luminous site: http://luminous.asgaard.co.uk
*
* User's documentation: http://luminous.asgaard.co.uk/index.php/docs/show/index
*
*
*/

View file

@ -0,0 +1,173 @@
<?php
/// @cond ALL
/**
* \file luminous_formatter.class.php
* \brief Formatting logic -- converts Luminous output into displayable formats
*/
/**
* \brief Abstract class to convert Luminous output into a universal format.
*
* Abstract base class to implement an output formatter. A formatter
* will convert Luminous's tags into some kind of output (e.g. HTML), by
* overriding the method Format().
*/
abstract class LuminousFormatter {
/// Number of chars to wrap at
public $wrap_length = 120;
/// Don't use this yet.
public $language_specific_tags = false;
/**
* Tab width, in spaces. If this is -1 or 0, tabs will not be converted. This
* is not recommended as browsers may render tabs as different widths which
* will break the wrapping.
*/
public $tab_width = 2;
/// Whether or not to add line numbering
public $line_numbers = true;
/// Number of first line
public $start_line = 1;
/// An array of lines to be highlighted initially, if the formatter supports
/// it
public $highlight_lines = array();
/// sets whether or not to link URIs.
public $link = true;
/**
* Height of the resulting output. This may or may not make any sense
* depending on the output format.
*
* Use 0 or -1 for no limit.
*/
public $height = 0;
/**
* The language of the source code being highlighted. Formatters may choose
* to do something with this.
*/
public $language = null;
/**
* The main method for interacting with formatter objects.
* @param src the input string, which is of the form output by an instance of
* Luminous.
* @return The input string reformatted to some other specification.
*/
public abstract function format($src);
/**
* If relevant, the formatter should implement this and use LuminousCSSParser
* to port the theme.
* @param $theme A CSS string representing the theme
*/
public function set_theme($theme)
{
}
/**
* @internal
* Handles line wrapping.
* @param line the line which needs to be broken. This is a reference, which
* will be operated upon. After calling, $line will have appropriate line
* breaks to wrap to the given width, and will contain at least one line break
* at the end.
* @param wrap_length the width to wrap to.
*
* @return the number of lines it was broken up into (1 obviously means no
* wrapping occurred.).
*
* @todo wrap to indent? or not? hm.
*
*/
protected static function wrap_line(&$line, $wrap_length) {
// The vast majority of lines will not need wrapping so it pays to
// check this first.
if ($wrap_length <= 0 || !isset($line[$wrap_length])
|| strlen(strip_tags($line)) < $wrap_length) {
$line .= "\n";
return 1;
}
$line_split = preg_split('/((?:<.*?>)|(?:&.*?;)|[ \t]+)/',
$line, -1, PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
$strlen = 0;
$line_cpy = "";
$num_lines = 1;
$num_open = 0;
foreach($line_split as $l) {
$l0 = $l[0];
if ($l0 === '<') {
$line_cpy .= $l;
continue;
}
$s = strlen($l);
if($l0 === '&') {
// html entity codes only count as 1 char.
if(++$strlen > $wrap_length) {
$strlen = 1;
$line_cpy .= "\n";
$num_lines++;
}
$line_cpy .= $l;
continue;
}
if ($s+$strlen <= $wrap_length) {
$line_cpy .= $l;
$strlen += $s;
continue;
}
if ($s <= $wrap_length) {
$line_cpy .= "\n" . $l;
$num_lines++;
$strlen = $s;
continue;
}
// at this point, the line needs wrapping.
// bump us up to the next line
$diff = $wrap_length-$strlen;
$line_cpy .= substr($l, 0, $diff) . "\n";
$l_ = substr($l, $diff);
// now start copying.
$strlen = 0;
// this would probably be marginally faster if it did its own arithmetic
// instead of calling strlen
while (strlen($l_) > 0) {
$strl = strlen($l_);
$num_lines++;
if ($strl > $wrap_length) {
$line_cpy .= substr($l_, 0, $wrap_length) . "\n";
$l_ = substr($l_, $wrap_length);
} else {
$line_cpy .= $l_;
$strlen = $strl;
break;
}
}
}
$line = $line_cpy . "\n";
return $num_lines;
}
}
/// @endcond

View file

@ -0,0 +1,344 @@
<?php
/// @cond ALL
/**
* Collection of templates and templating utilities
*/
class LuminousHTMLTemplates {
// NOTE Don't worry about whitespace in the templates - it gets stripped from the innerHTML,
// so the <pre>s aren't affected. Make it readable :)
/// Normal container
const container_template = '
<div
class="luminous"
data-language="{language}"
style="{height_css}"
>
{subelement}
</div>';
/// Inline code container
const inline_template = '
<div
class="luminous inline"
data-language="{language}"
>
{subelement}
</div>';
/// line number-less
const numberless_template = '
<pre
class="code"
>
{code}
</pre>';
/// line numbered
// NOTE: there's a good reason we use tables here and that's because
// nothing else works reliably.
const numbered_template = '
<table>
<tbody>
<tr>
<td>
<pre class="line-numbers">
{line_numbers}
</pre>
</td>
<td class="code-container">
<pre class="code numbered"
data-startline="{start_line}"
data-highlightlines="{highlight_lines}"
>
{code}
</pre>
</td>
</tr>
</tbody>
</table>';
private static function _strip_template_whitespace_cb($matches) {
return ($matches[0][0] === '<')? $matches[0] : '';
}
private static function _strip_template_whitespace($string) {
return preg_replace_callback('/\s+|<[^>]++>/',
array('self', '_strip_template_whitespace_cb'),
$string);
}
/**
* Formats a string with a given set of values
* The format syntax uses {xyz} as a placeholder, which will be
* substituted from the 'xyz' key from $variables
*
* @param $template The template string
* @param $variables An associative (keyed) array of values to be substituted
* @param $strip_whitespace_from_template If @c TRUE, the template's whitespace is removed.
* This allows templates to be written to be easeier to read, without having to worry about
* the pre element inherting any unintended whitespace
*/
public static function format($template, $variables, $strip_whitespace_from_template = true) {
if ($strip_whitespace_from_template) {
$template = self::_strip_template_whitespace($template);
}
foreach($variables as $search => $replace) {
$template = str_replace("{" . $search . "}", $replace, $template);
}
return $template;
}
}
class LuminousFormatterHTML extends LuminousFormatter {
// overridden by inline formatter
protected $inline = false;
public $height = 0;
/**
* strict HTML standards: the target attribute won't be used in links
* \since 0.5.7
*/
public $strict_standards = false;
private function height_css() {
$height = trim('' . $this->height);
$css = '';
if (!empty($height) && (int)$height > 0) {
// look for units, use px is there are none
if (!preg_match('/\D$/', $height)) $height .= 'px';
$css = "max-height: {$height};";
}
else
$css = '';
return $css;
}
private static function template_cb($matches) {
}
// strips out unnecessary whitespace from a template
private static function template($t, $vars=array()) {
$t = preg_replace_callback('/\s+|<[^>]++>/',
array('self', 'template_cb'),
$t);
array_unshift($vars, $t);
$code = call_user_func_array('sprintf', $vars);
return $code;
}
private function lines_numberless($src) {
$lines = array();
$lines_original = explode("\n", $src);
foreach($lines_original as $line) {
$l = $line;
$num = $this->wrap_line($l, $this->wrap_length);
// strip the newline if we're going to join it. Seems the easiest way to
// fix http://code.google.com/p/luminous/issues/detail?id=10
$l = substr($l, 0, -1);
$lines[] = $l;
}
$lines = implode("\n", $lines);
return $lines;
}
private function format_numberless($src) {
return LuminousHTMLTemplates::format(
LuminousHTMLTemplates::numberless_template,
array(
'height_css' => $this->height_css(),
'code' => $this->lines_numberless($src)
)
);
}
public function format($src) {
$line_numbers = false;
if ($this->link) $src = $this->linkify($src);
$code_block = null;
if ($this->line_numbers) {
$code_block = $this->format_numbered($src);
}
else {
$code_block = $this->format_numberless($src);
}
// convert </ABC> to </span>
$code_block = preg_replace('/(?<=<\/)[A-Z_0-9]+(?=>)/S', 'span',
$code_block);
// convert <ABC> to <span class=ABC>
$cb = create_function('$matches',
'$m1 = strtolower($matches[1]);
return "<span class=\'" . $m1 . "\'>";
');
$code_block = preg_replace_callback('/<([A-Z_0-9]+)>/', $cb, $code_block);
$format_data = array(
'language' => ($this->language === null)? '' : htmlentities($this->language),
'subelement' => $code_block,
'height_css' => $this->height_css()
);
return LuminousHTMLTemplates::format(
$this->inline? LuminousHTMLTemplates::inline_template :
LuminousHTMLTemplates::container_template,
$format_data
);
}
/**
* Detects and links URLs - callback
*/
protected function linkify_cb($matches) {
$uri = (isset($matches[1]) && strlen(trim($matches[1])))? $matches[0]
: "http://" . $matches[0];
// we dont want to link if it would cause malformed HTML
$open_tags = array();
$close_tags = array();
preg_match_all("/<(?!\/)([^\s>]*).*?>/", $matches[0], $open_tags,
PREG_SET_ORDER);
preg_match_all("/<\/([^\s>]*).*?>/", $matches[0], $close_tags,
PREG_SET_ORDER);
if (count($open_tags) != count($close_tags))
return $matches[0];
if (isset($open_tags[0])
&& trim($open_tags[0][1]) !== trim($close_tags[0][1])
)
return $matches[0];
$uri = strip_tags($uri);
$target = ($this->strict_standards)? '' : ' target="_blank"';
return "<a href='{$uri}' class='link'{$target}>{$matches[0]}</a>";
}
/**
* Detects and links URLs
*/
protected function linkify($src) {
if (stripos($src, "http") === false && stripos($src, "www") === false)
return $src;
$chars = "0-9a-zA-Z\$\-_\.+!\*,%";
$src_ = $src;
// everyone stand back, I know regular expressions
$src = preg_replace_callback(
"@(?<![\w])
(?:(https?://(?:www[0-9]*\.)?) | (?:www\d*\.) )
# domain and tld
(?:[$chars]+)+\.[$chars]{2,}
# we don't include tags at the EOL because these are likely to be
# line-enclosing tags.
(?:[/$chars/?=\#;]+|&amp;|<[^>]+>(?!$))*
@xm",
array($this, 'linkify_cb'), $src);
// this can hit a backtracking limit, in which case it nulls our string
// FIXME: see if we can make the above regex more resiliant wrt
// backtracking
if (preg_last_error() !== PREG_NO_ERROR) {
$src = $src_;
}
return $src;
}
private function format_numbered($src) {
$lines = '<span>' .
str_replace("\n", "\n</span><span>", $src, $num_replacements) .
"\n</span>";
$num_lines = $num_replacements + 1;
$line_numbers = '<span>' . implode('</span><span>',
range($this->start_line, $this->start_line + $num_lines - 1, 1)
) . '</span>';
$format_data = array(
'line_number_digits' => strlen( (string)($this->start_line) + $num_lines ), // max number of digits in the line - this is used by the CSS
'start_line' => $this->start_line,
'height_css' => $this->height_css(),
'highlight_lines' => implode(',', $this->highlight_lines),
'code' => $lines,
'line_numbers' => $line_numbers
);
return LuminousHTMLTemplates::format(
LuminousHTMLTemplates::numbered_template,
$format_data
);
}
}
class LuminousFormatterHTMLInline extends LuminousFormatterHTML {
public function format($src) {
$this->line_numbers = false;
$this->height = 0;
$this->inline = true;
return parent::format($src);
}
}
class LuminousFormatterHTMLFullPage extends LuminousFormatterHTML {
protected $theme_css = null;
protected $css = null;
public function set_theme($css) {
$this->theme_css = $css;
}
protected function get_layout() {
// this path info shouldn't really be here
$path = luminous::root() . '/style/luminous.css';
$this->css = file_get_contents($path);
}
public function format($src) {
$this->height = 0;
$this->get_layout();
$fmted = parent::format($src);
return <<<EOF
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title></title>
<style type='text/css'>
body {
margin: 0;
}
/* luminous.css */
{$this->css}
/* End luminous.css */
/* Theme CSS */
{$this->theme_css}
/* End theme CSS */
</style>
</head>
<body>
<!-- Begin luminous code //-->
$fmted
<!-- End Luminous code //-->
</body>
</html>
EOF;
}
}
/// @endcond

View file

@ -0,0 +1,14 @@
<?php
/// @cond ALL
/**
* Identity formatter. Returns what it's given. Implemented for consistency.
*/
class LuminousIdentityFormatter extends LuminousFormatter {
public function format($str) {
return $str;
}
}
/// @endcond

View file

@ -0,0 +1,200 @@
<?php
/// @cond ALL
require_once(dirname(__FILE__) . '/../utils/cssparser.class.php');
/**
* LaTeX output formatter for Luminous.
*
* \since 0.5.4
*/
class LuminousFormatterLatex extends LuminousFormatter {
private $css = null;
function __construct() { }
function set_theme($theme) {
$this->css = new LuminousCSSParser();
$this->css->convert($theme);
}
/// Converts a hexadecimal string in the form #ABCDEF to an RGB array
/// where each element is normalised to the range 0-1
static function hex2rgb($hex) {
$x = hexdec(substr($hex, 1));
$b = $x % 256;
$g = ($x >> 8) % 256;
$r = ($x >> 16) % 256;
$b /= 255.0;
$g /= 255.0;
$r /= 255.0;
$b = round($b, 2);
$g = round($g, 2);
$r = round($r, 2);
$rgb = array($r, $g, $b);
return $rgb;
}
protected function linkify($src) {
return $src;
}
/// Defines all the styling commands, these are obtained from the css parser
function define_style_commands() {
if ($this->css === null)
throw new Exception('LaTeX formatter has not been set a theme');
$cmds = array();
foreach($this->css->rules() as $name=>$properties) {
if (!preg_match('/^\w+$/', $name))
continue;
$cmd = "{#1}" ;
if ($this->css->value($name, 'bold', false) === true)
$cmd = "{\\textbf$cmd}";
if ($this->css->value($name, 'italic', false) === true)
$cmd = "{\\emph$cmd}";
if (($col = $this->css->value($name, 'color', null)) !== null) {
if (preg_match('/^#[a-f0-9]{6}$/i', $col)) {
$rgb = self::hex2rgb($col);
$col_str = "{$rgb[0]}, {$rgb[1]}, $rgb[2]";
$cmd = "{\\textcolor[rgb]{{$col_str}}$cmd}";
}
}
$name = str_replace('_', '', $name);
$name = strtoupper($name);
$cmds[] = "\\newcommand{\\lms{$name}}[1]$cmd";
}
if ($this->line_numbers &&
($col = $this->css->value('code', 'color', null)) !== null) {
if (preg_match('/^#[a-f0-9]{6}$/i', $col)) {
$rgb = self::hex2rgb($col);
$col_str = "{$rgb[0]}, {$rgb[1]}, $rgb[2]";
$cmd = "\\renewcommand{\\theFancyVerbLine}{%
\\textcolor[rgb]{{$col_str}}{\arabic{FancyVerbLine}}}";
$cmds[] = $cmd;
}
}
return implode("\n", $cmds);
}
function get_background_colour() {
if (($col = $this->css->value('code', 'bgcolor', null)) !== null) {
if (preg_match('/^#[a-f0-9]{6}$/i', $col))
$rgb = self::hex2rgb($col);
$col_str = "{$rgb[0]}, {$rgb[1]}, $rgb[2]";
return "\\pagecolor[rgb]{{$col_str}}";
}
return "";
}
function format($str) {
$out = '';
$verbcmd = "\\begin{Verbatim}[commandchars=\\\\\\{\}";
if ($this->line_numbers)
$verbcmd .= ",numbers=left,firstnumber=1,stepnumber=1";
$verbcmd .= ']';
// define the preamble
$out .= <<<EOF
\documentclass{article}
\usepackage{fullpage}
\usepackage{color}
\usepackage{fancyvrb}
\begin{document}
{$this->define_style_commands()}
{$this->get_background_colour()}
$verbcmd
EOF;
$s = '';
$str = preg_replace('%<([^/>]+)>\s*</\\1>%', '', $str);
$str = str_replace("\t", ' ', $str);
$lines = explode("\n", $str);
if ($this->wrap_length > 0) {
$str = '';
foreach($lines as $i=>$l) {
$this->wrap_line($l, $this->wrap_length);
$str .= $l;
}
}
$str_ = preg_split('/(<[^>]+>)/', $str, -1,
PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
$f1 = create_function('$matches', '
return "\\\lms" . str_replace("_", "", $matches[1]) . "{"; ');
$f2 = create_function('$matches', '
if ($matches[0][0] == "\\\")
return "{\\\textbackslash}";
return "\\\" . $matches[0];');
foreach($str_ as $s_) {
if ($s_[0] === '<') {
$s_ = preg_replace('%</[^>]+>%', '}', $s_);
$s_ = preg_replace_callback('%<([^>]+)>%', $f1
,$s_);
} else {
$s_ = str_replace('&gt;', '>', $s_);
$s_ = str_replace('&lt;', '<', $s_);
$s_ = str_replace('&amp;', '&', $s_);
$s_ = preg_replace_callback('/[#{}_$\\\&]|&(?=amp;)/', $f2, $s_);
}
$s .= $s_;
}
unset($str_);
$s = "\\lmsCODE{" . $s . '}';
/* XXX:
* hack alert: leaving newline literals (\n) inside arguments seems to
* leave them being totally ignored. This is a problem for wrapping.
*
* the current solution is to close all open lms commands before the
* newline then reopen them afterwards.
*/
$stack = array();
$pieces = preg_split('/(\\\lms[^\{]+\{|(?<!\\\)(\\\\\\\\)*[\{\}])/', $s,
-1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
// NOTE: p being a reference is probably going to necessitate a lot of
// copying to pass through all these preg_* and str* calls.
// consider rewriting.
foreach($pieces as $k=>&$p) {
if (preg_match('/^\\\lms/', $p))
$stack[] = "" . $p;
elseif(preg_match('/^(\\\\\\\\)*\}/', $p)) {
array_pop($stack);
}
elseif(preg_match('/^(\\\\\\\\)*{/', $p))
$stack [] = $p;
elseif(strpos($p, "\n") !== false) {
$before = "";
$after = "";
foreach($stack as $st_) {
$before .= $st_;
$after .= '}';
}
$p = str_replace("\n", "$after\n$before" , $p);
}
}
$s = implode('', $pieces);
$out .= $s;
$out .= <<<EOF
\end{Verbatim}
\end{document}
EOF;
return $out;
}
}
/// @endcond

View file

@ -0,0 +1,140 @@
<?php
/*
* This is a horrible routine to register all the default
* scanners. The code is distracting at best so it's been factored into this one
* file.
*
* We include it into the main program with a require statement, which
* due to the literal way PHP includes work, when done within a function gives
* us access to that function's scope.
* We are in the scope of a method inside the Luminous_ object, so we refer to
* $this as being the $luminous_ singleton object.
*/
$language_dir = luminous::root() . '/languages/';
// this is a dummy file which includes ECMAScript dependencies in a
// non-circular way.
$this->scanners->AddScanner('ecma-includes', null, null,
"$language_dir/include/ecma.php");
$this->scanners->AddScanner(array('ada', 'adb', 'ads'),
'LuminousAdaScanner', 'Ada', "$language_dir/ada.php");
$this->scanners->AddScanner(array('as', 'actionscript'),
'LuminousActionScriptScanner', 'ActionScript', "$language_dir/as.php",
'ecma');
$this->scanners->AddScanner(array('bnf'),
'LuminousBNFScanner', 'Backus Naur Form', "$language_dir/bnf.php");
$this->scanners->AddScanner(array('bash', 'sh'),
'LuminousBashScanner', 'Bash', "$language_dir/bash.php");
$this->scanners->AddScanner(array('c', 'cpp', 'h', 'hpp', 'cxx', 'hxx'),
'LuminousCppScanner', 'C/C++', "$language_dir/cpp.php");
$this->scanners->AddScanner(array('cs', 'csharp', 'c#'),
'LuminousCSharpScanner', 'C#', "$language_dir/csharp.php");
$this->scanners->AddScanner('css',
'LuminousCSSScanner', 'CSS', "$language_dir/css.php");
$this->scanners->AddScanner(array('diff', 'patch'),
'LuminousDiffScanner', 'Diff', "$language_dir/diff.php");
$this->scanners->AddScanner(array('prettydiff', 'prettypatch',
'diffpretty', 'patchpretty'),
'LuminousPrettyDiffScanner', 'Diff-Pretty', "$language_dir/diff.php");
$this->scanners->AddScanner(array('html', 'htm'),
'LuminousHTMLScanner', 'HTML', "$language_dir/html.php",
array('js', 'css'));
$this->scanners->AddScanner(array('ecma', 'ecmascript'),
'LuminousECMAScriptScanner', 'ECMAScript',
"$language_dir/ecmascript.php", 'ecma-includes');
$this->scanners->AddScanner(array('erlang', 'erl', 'hrl'),
'LuminousErlangScanner', 'Erlang', "$language_dir/erlang.php");
$this->scanners->AddScanner('go', 'LuminousGoScanner', 'Go',
"$language_dir/go.php");
$this->scanners->AddScanner(array('groovy'),
'LuminousGroovyScanner', 'Groovy',
"$language_dir/groovy.php");
$this->scanners->AddScanner(array('haskell', 'hs'),
'LuminousHaskellScanner', 'Haskell', "$language_dir/haskell.php");
$this->scanners->AddScanner('java',
'LuminousJavaScanner', 'Java', "$language_dir/java.php");
$this->scanners->AddScanner(array('js', 'javascript'),
'LuminousJavaScriptScanner', 'JavaScript', "$language_dir/javascript.php",
array('ecma'));
$this->scanners->AddScanner('json',
'LuminousJSONScanner', 'JSON', "$language_dir/json.php");
$this->scanners->AddScanner(array('latex', 'tex'),
'LuminousLatexScanner', 'LaTeX', "$language_dir/latex.php");
$this->scanners->AddScanner(array('lolcode', 'lolc', 'lol'),
'LuminousLOLCODEScanner', 'LOLCODE', "$language_dir/lolcode.php");
$this->scanners->AddScanner(array('m', 'matlab'),
'LuminousMATLABScanner', 'MATLAB', "$language_dir/matlab.php");
$this->scanners->AddScanner(array('perl', 'pl', 'pm'),
'LuminousPerlScanner', 'Perl', "$language_dir/perl.php");
$this->scanners->AddScanner(array('rails','rhtml', 'ror'),
'LuminousRailsScanner', 'Ruby on Rails',
"$language_dir/rails.php", array('ruby', 'html'));
$this->scanners->AddScanner(array('ruby','rb'),
'LuminousRubyScanner', 'Ruby', "$language_dir/ruby.php");
$this->scanners->AddScanner(array('plain', 'text', 'txt'),
'LuminousIdentityScanner', 'Plain', "$language_dir/identity.php");
// PHP Snippet does not require an initial <?php tag to begin highlighting
$this->scanners->AddScanner('php_snippet', 'LuminousPHPSnippetScanner',
'PHP Snippet', "$language_dir/php.php", array('html'));
$this->scanners->AddScanner('php',
'LuminousPHPScanner', 'PHP', "$language_dir/php.php",
array('html'));
$this->scanners->AddScanner(array('python', 'py'),
'LuminousPythonScanner', 'Python', "$language_dir/python.php");
$this->scanners->AddScanner(array('django', 'djt'),
'LuminousDjangoScanner', 'Django', "$language_dir/python.php",
array('html')
);
$this->scanners->AddScanner(array('scala', 'scl'),
'LuminousScalaScanner', 'Scala', "$language_dir/scala.php", 'xml');
$this->scanners->AddScanner('scss',
'LuminousSCSSScanner', 'SCSS', "$language_dir/scss.php");
$this->scanners->AddScanner(array('sql', 'mysql'),
'LuminousSQLScanner', 'SQL', "$language_dir/sql.php");
$this->scanners->AddScanner(array('vim', 'vimscript'),
'LuminousVimScriptScanner', 'Vim Script', "$language_dir/vim.php");
$this->scanners->AddScanner(array('vb', 'bas'),
'LuminousVBScanner', 'Visual Basic', "$language_dir/vb.php",
'xml');
$this->scanners->AddScanner('xml', 'LuminousXMLScanner',
'XML', "$language_dir/xml.php", 'html');
$this->scanners->SetDefaultScanner('plain');

View file

@ -0,0 +1,626 @@
<?php
require_once(dirname(__FILE__) . '/debug.php');
require_once(dirname(__FILE__) . '/options.class.php');
require_once(dirname(__FILE__) . '/cache/cache.class.php');
require_once(dirname(__FILE__) . '/cache/fscache.class.php');
require_once(dirname(__FILE__) . '/cache/sqlcache.class.php');
require_once(dirname(__FILE__) . '/scanners.class.php');
require_once(dirname(__FILE__) . '/formatters/formatter.class.php');
require_once(dirname(__FILE__) . '/core/scanner.class.php');
// updated automatically, use single quotes, keep single line
define('LUMINOUS_VERSION', 'v0.7.0');
/*
* This file contains the public calling interface for Luminous. It's split
* into two classes: one is basically the user-interface, the other is a
* wrapper around it. The wrapper allows a single-line function call, and is
* procedural. It's wrapped in an abstract class for a namespace.
* The real class is instantiated into a singleton which is manipulated by
* the abstract class methods.
*/
/**
* @cond ALL
* This is kind of a pseudo-UI class. It's a singleton which will be
* manipulated by a few procedural functions, for ease of use.
* It's technically supposed to be private to this class, but this is a sort
* of 'agreed' privateness, and we expose an instance of it globally.
*
* In fact, it is used by (at least) the diff scanner, which uses its
* scanner table.
*
* @internal
*
*/
class _Luminous {
/// Settings array
/// @see LuminousOptions
public $settings;
public $scanners; ///< the scanner table
public $cache = null;
/**
* The language is passed to the formatter which may choose to do something
* interesting with it. If you use the scanners table this can be figured out
* automatically, but if you pass in your own scanner, you will need to
* give a language name if you want the formatter to consider it.
*/
public $language = null;
public function __construct() {
$this->scanners = new LuminousScanners();
$this->register_default_scanners();
}
/// registers builtin scanners
private function register_default_scanners() {
$this->settings = new LuminousOptions();
require dirname(__FILE__) . '/load_scanners.php';
}
/**
* Returns an instance of the current formatter
*/
function get_formatter() {
$fmt_path = dirname(__FILE__) . '/formatters/';
$fmt = $this->settings->format;
$formatter = null;
if (!is_string($fmt) && is_subclass_of($fmt, 'LuminousFormatter')) {
$formatter = clone $fmt;
} elseif ($fmt === 'html') {
require_once($fmt_path . 'htmlformatter.class.php');
$formatter = new LuminousFormatterHTML();
} elseif ($fmt === 'html-inline') {
require_once($fmt_path . 'htmlformatter.class.php');
$formatter = new LuminousFormatterHTMLInline();
} elseif ($fmt === 'html-full') {
require_once($fmt_path . 'htmlformatter.class.php');
$formatter = new LuminousFormatterHTMLFullPage();
} elseif($fmt === 'latex') {
require_once($fmt_path . 'latexformatter.class.php');
$formatter = new LuminousFormatterLatex();
} elseif($fmt === null || $fmt === 'none') {
require_once($fmt_path . 'identityformatter.class.php');
$formatter = new LuminousIdentityFormatter();
}
if ($formatter === null) {
throw new Exception('Unknown formatter: ' . $this->settings->format);
return null;
}
$this->set_formatter_options($formatter);
return $formatter;
}
/**
* Sets up a formatter instance according to our current options/settings
*/
private function set_formatter_options(&$formatter) {
$formatter->wrap_length = $this->settings->wrap_width;
$formatter->line_numbers = $this->settings->line_numbers;
$formatter->start_line = $this->settings->start_line;
$formatter->link = $this->settings->auto_link;
$formatter->height = $this->settings->max_height;
$formatter->strict_standards = $this->settings->html_strict;
$formatter->set_theme(luminous::theme($this->settings->theme));
$formatter->highlight_lines = $this->settings->highlight_lines;
$formatter->language = $this->language;
}
/**
* calculates a 'cache_id' for the input. This is dependent upon the
* source code and the settings. This should be (near-as-feasible) unique
* for any cobmination of source, language and settings
*/
private function cache_id($scanner, $source) {
// to figure out the cache id, we mash a load of stuff together and
// md5 it. This gives us a unique (assuming no collisions) handle to
// a cache file, which depends on the input source, the relevant formatter
// settings, the version, and scanner.
$settings = array($this->settings->wrap_width,
$this->settings->line_numbers,
$this->settings->start_line,
$this->settings->auto_link,
$this->settings->max_height,
$this->settings->format,
$this->settings->theme,
$this->settings->html_strict,
LUMINOUS_VERSION,
);
$id = md5($source);
$id = md5($id . serialize($scanner));
$id = md5($id . serialize($settings));
return $id;
}
/**
* The real highlighting function
* @throw InvalidArgumentException if $scanner is not either a string or a
* LuminousScanner instance, or if $source is not a string.
*/
function highlight($scanner, $source, $settings = null) {
$old_settings = null;
if ($settings !== null) {
if (!is_array($settings)) {
throw new Exception('Luminous internal error: Settings is not an array');
}
$old_settings = clone $this->settings;
foreach($settings as $k=>$v) {
$this->settings->set($k, $v);
}
}
$should_reset_language = false;
$this->cache = null;
if (!is_string($source)) throw new InvalidArgumentException('Non-string '
. 'supplied for $source');
if (!($scanner instanceof LuminousScanner)) {
if (!is_string($scanner)) throw new InvalidArgumentException('Non-string
or LuminousScanner instance supplied for $scanner');
$code = $scanner;
$scanner = $this->scanners->GetScanner($code);
if ($scanner === null) throw new Exception("No known scanner for '$code' and no default set");
$should_reset_language = true;
$this->language = $this->scanners->GetDescription($code);
}
$cache_hit = true;
$out = null;
if ($this->settings->cache) {
$cache_id = $this->cache_id($scanner, $source);
if ($this->settings->sql_function !== null) {
$this->cache = new LuminousSQLCache($cache_id);
$this->cache->set_sql_function($this->settings->sql_function);
} else {
$this->cache = new LuminousFileSystemCache($cache_id);
}
$this->cache->set_purge_time($this->settings->cache_age);
$out = $this->cache->read();
}
if ($out === null) {
$cache_hit = false;
$out_raw = $scanner->highlight($source);
$formatter = $this->get_formatter();
$out = $formatter->format($out_raw);
}
if ($this->settings->cache && !$cache_hit) {
$this->cache->write($out);
}
if ($should_reset_language) $this->language = null;
if ($old_settings !== null) {
$this->settings = $old_settings;
}
return $out;
}
}
/// @endcond
// ends ALL
// Here's our singleton.
global $luminous_; // sometimes need this or the object seems to disappear
$luminous_ = new _Luminous();
/// @cond USER
// here's our 'real' UI class, which uses the above singleton. This is all
// static because these are actually procudural functions, we're using the
// class as a namespace.
/**
* @brief Users' API
*/
abstract class luminous {
/**
* @brief Highlights a string according to the current settings
*
* @param $scanner The scanner to use, this can either be a langauge code,
* or it can be an instance of LuminousScanner.
* @param $source The source string
* @param $cache Whether or not to use the cache
* @return the highlighted source code.
*
* To specify different output formats or other options, see set().
*/
static function highlight($scanner, $source, $cache_or_settings=null) {
global $luminous_;
try {
$settings = null;
if (is_bool($cache_or_settings)) {
$settings = array('cache' => $cache_or_settings);
} else if (is_array($cache_or_settings)) {
$settings = $cache_or_settings;
}
$h = $luminous_->highlight($scanner, $source, $settings);
if ($luminous_->settings->verbose) {
$errs = self::cache_errors();
if (!empty($errs)) {
trigger_error("Luminous cache errors were encountered. \n" .
'See luminous::cache_errors() for details.');
}
}
return $h;
} catch (InvalidArgumentException $e) {
// this is a user error, let it bubble
//FIXME how do we let it bubble without throwing? the stack trace will
// be wrong.
throw $e;
}
catch (Exception $e) {
// this is an internal error or a scanner error, or something
// it might not technically be Luminous that caused it, but let's not
// make it kill the whole page in production code
if (LUMINOUS_DEBUG) throw $e;
else {
$return = $source;
if (($t = self::setting('failure-tag')))
$return = "<$t>$return</$t>";
return $return;
}
}
}
/**
* @brief Highlights a file according to the current setings.
*
* @param $scanner The scanner to use, this can either be a langauge code,
* or it can be an instance of LuminousScanner.
* @param $file the source string
* @param $cache Whether or not to use the cache
* @return the highlighted source code.
*
* To specify different output formats or other options, see set().
*/
static function highlight_file($scanner, $file, $cache_or_settings=null) {
return self::highlight($scanner, file_get_contents($file), $cache_or_settings);
}
/**
* @brief Returns a list of cache errors encountered during the most recent highlight
*
* @return An array of errors the cache encountered (which may be empty),
* or @c FALSE if the cache is not enabled
*/
static function cache_errors() {
global $luminous_;
$c = $luminous_->cache;
if ($c === null) return FALSE;
return $c->errors();
}
/**
* @brief Registers a scanner
*
* Registers a scanner with Luminous's scanner table. Utilising this
* function means that Luminous will handle instantiation and inclusion of
* the scanner's source file in a lazy-manner.
*
* @param $language_code A string or array of strings designating the
* aliases by which this scanner may be accessed
* @param $classname The class name of the scanner, as string. If you
* leave this as 'null', it will be treated as a dummy file (you can use
* this to handle a set of non-circular include rules, if you run into
* problems).
* @param $readable_language A human readable language name
* @param $path The path to the source file containing your scanner
* @param dependencies An array of other scanners which this scanner
* depends on (as sub-scanners, or superclasses). Each item in the
* array should be a $language_code for another scanner.
*/
static function register_scanner($language_code, $classname,
$readable_language, $path, $dependencies=null) {
global $luminous_;
$luminous_->scanners->AddScanner($language_code, $classname,
$readable_language, $path, $dependencies);
}
/**
* @brief Get the full filesystem path to Luminous
* @return what Luminous thinks its location is on the filesystem
* @internal
*/
static function root() {
return realpath(dirname(__FILE__) . '/../');
}
/**
* @brief Gets a list of installed themes
*
* @return the list of theme files present in style/.
* Each theme will simply be a filename, and will end in .css, and will not
* have any directory prefix.
*/
static function themes() {
$themes_uri = self::root() . '/style/';
$themes = array();
foreach(glob($themes_uri . '/*.css') as $css) {
$fn = trim(preg_replace("%.*/%", '', $css));
switch($fn) {
// these are special, exclude these
case 'luminous.css':
case 'luminous_print.css':
case 'luminous.min.css':
continue;
default:
$themes[] = $fn;
}
}
return $themes;
}
/**
* @brief Checks whether a theme exists
* @param $theme the name of a theme, which should be suffixed with .css
* @return @c TRUE if a theme exists in style/, else @c FALSE
*/
static function theme_exists($theme) {
return in_array($theme, self::themes());
}
/**
* @brief Reads a CSS theme file
* Gets the CSS-string content of a theme file.
* Use this function for reading themes as it involves security
* checks against reading arbitrary files
*
* @param $theme The name of the theme to retrieve, which may or may not
* include the .css suffix.
* @return the content of a theme; this is the actual CSS text.
* @internal
*/
static function theme($theme) {
if (!preg_match('/\.css$/i', $theme)) $theme .= '.css';
if (self::theme_exists($theme))
return file_get_contents(self::root() . "/style/" . $theme);
else
throw new Exception('No such theme file: ' . $theme);
}
/**
* @brief Gets a setting's value
* @param $option The name of the setting (corresponds to an attribute name
* in LuminousOptions)
* @return The value of the given setting
* @throws Exception if the option is unrecognised
*
* Options are stored in LuminousOptions, which provides documentation of
* each option.
* @see LuminousOptions
*/
static function setting($option) {
global $luminous_;
$option = str_replace('-', '_', $option);
return $luminous_->settings->$option;
}
/**
* @brief Sets the given option to the given value
* @param $option The name of the setting (corresponds to an attribute name
* in LuminousOptions)
* @param $value The new value of the setting
* @throws Exception if the option is unrecognised (and in various other
* validation failures),
* @throws InvalidArgumentException if the argument fails the type-validation
* check
*
* @note This function can also accept multiple settings if $option is a
* map of option_name=>value
*
* Options are stored in LuminousOptions, which provides documentation of
* each option.
*
* @note as of 0.7 this is a thin wrapper around LuminousOptions::set()
*
* @see LuminousOptions::set
*/
static function set($option, $value=null) {
global $luminous_;
$luminous_->settings->set($option, $value);
}
/**
* @brief Gets a list of registered scanners
*
* @return a list of scanners currently registered. The list is in the
* format:
*
* language_name => codes,
*
* where language_name is a string, and codes is an array of strings.
*
* The array is sorted alphabetically by key.
*/
static function scanners() {
global $luminous_;
$scanners = $luminous_->scanners->ListScanners();
ksort($scanners);
return $scanners;
}
/**
* @brief Gets a formatter instance
*
* @return an instance of a LuminousFormatter according to the current
* format setting
*
* This shouldn't be necessary for general usage, it is only implemented
* for testing.
* @internal
*/
static function formatter() {
global $luminous_;
return $luminous_->get_formatter();
}
/**
* @internal
* Comparison function for guess_language()'s sorting
*/
static function __guess_language_cmp($a, $b) {
$c = $a['p'] - $b['p'];
if ($c === 0) return 0;
elseif ($c < 0) return -1;
else return 1;
}
/**
* @brief Attempts to guess the language of a piece of source code
* @param $src The source code whose language is to be guessed
* @param $confidence The desired confidence level: if this is 0.05 but the
* best guess has a confidence of 0.04, then $default is returned. Note
* that the confidence level returned by scanners is quite arbitrary, so
* don't set this to '1' thinking that'll give you better results.
* A realistic confidence is likely to be quite low, because a scanner will
* only return 1 if it's able to pick out a shebang (#!) line or something
* else definitive. If there exists no such identifier, a 'strong'
* confidence which is right most of the time might be as low as 0.1.
* Therefore it is recommended to keep this between 0.01 and 0.10.
* @param $default The default name to return in the event that no scanner
* thinks this source belongs to them (at the desired confidence).
*
* @return A valid code for the best scanner, or $default.
*
* This is a wrapper around luminous::guess_language_full
*/
static function guess_language($src, $confidence=0.05, $default = 'plain') {
$guess = self::guess_language_full($src);
if ($guess[0]['p'] >= $confidence)
return $guess[0]['codes'][0];
else
return $default;
}
/**
* @brief Attempts to guess the language of a piece of source code
* @param $src The source code whose language is to be guessed
* @return An array - the array is ordered by probability, with the most
* probable language coming first in the array.
* Each array element is an array which represents a language (scanner),
* and has the keys:
* \li \c 'language' => Human-readable language description,
* \li \c 'codes' => valid codes for the language (array),
* \li \c 'p' => the probability (between 0.0 and 1.0 inclusive),
*
* note that \c 'language' and \c 'codes' are the key => value pair from
* luminous::scanners()
*
* @warning Language guessing is inherently unreliable but should be right
* about 80% of the time on common languages. Bear in mind that guessing is
* going to be severely hampered in the case that one language is used to
* generate code in another language.
*
* Usage for this function will be something like this:
* @code
* $guesses = luminous::guess_language($src);
* $output = luminous::highlight($guesses[0]['codes'][0], $src);
* @endcode
*
* @see luminous::guess_language
*/
static function guess_language_full($src) {
global $luminous_;
// first we're going to make an 'info' array for the source, which
// precomputes some frequently useful things, like how many lines it
// has, etc. It prevents scanners from redundantly figuring these things
// out themselves
$lines = preg_split("/\r\n|[\r\n]/", $src);
$shebang = '';
if (preg_match('/^#!.*/', $src, $m)) $shebang = $m[0];
$info = array(
'lines' => $lines,
'num_lines' => count($lines),
'trimmed' => trim($src),
'shebang' => $shebang
);
$return = array();
foreach(self::scanners() as $lang=>$codes) {
$scanner_name = $luminous_->scanners->GetScanner($codes[0], false,
false);
assert($scanner_name !== null);
$return[] = array(
'language' => $lang,
'codes' => $codes,
'p' => call_user_func(array($scanner_name, 'guess_language'), $src,
$info)
);
}
uasort($return, array('luminous', '__guess_language_cmp'));
$return = array_reverse($return);
return $return;
}
/**
* @brief Gets the markup you need to include in your web page
* @return a string representing everything that needs to be printed in
* the \<head\> section of a website.
*
* This is influenced by the following settings:
* relative-root,
* include-javascript,
* include-jquery
* theme
*/
static function head_html() {
global $luminous_;
$theme = self::setting('theme');
$relative_root = self::setting('relative-root');
$js = self::setting('include-javascript');
$jquery = self::setting('include-jquery');
if (!preg_match('/\.css$/i', $theme)) $theme .= '.css';
if (!self::theme_exists($theme)) $theme = 'luminous_light.css';
if ($relative_root === null) {
$relative_root = str_replace($_SERVER['DOCUMENT_ROOT'], '/',
dirname(__FILE__));
$relative_root = str_replace('\\', '/', $relative_root); // bah windows
$relative_root = rtrim($relative_root, '/');
// go up one level.
$relative_root = preg_replace('%/[^/]*$%', '', $relative_root);
}
// if we ended up with any double slashes, let's zap them, and also
// trim any trailing ones
$relative_root = preg_replace('%(?<!:)//+%', '/', $relative_root);
$relative_root = rtrim($relative_root, '/');
$out = '';
$link_template = "<link rel='stylesheet' type='text/css' href='$relative_root/style/%s' id='%s'>\n";
$script_template = "<script type='text/javascript' src='$relative_root/client/%s'></script>\n";
$out .= sprintf($link_template, 'luminous.css', 'luminous-style');
$out .= sprintf($link_template, $theme, 'luminous-theme');
if ($js) {
if ($jquery)
$out .= sprintf($script_template, 'jquery-1.6.4.min.js');
$out .= sprintf($script_template, 'luminous.js');
}
return $out;
}
}
/// @endcond
// ends user

View file

@ -0,0 +1,342 @@
<?php
/**
* @cond USER
*
* @brief Options class.
*
* @warning This object's structure isn't guaranteed to be stable so don't read
* or write these directly. As a user, you should be using luminous::set()
* and luminous::setting()
*
* We use a fair bit of PHP trickery in the implementation here. The keener
* among you will notice that the options are all private: don't worry about
* that. We override the __set() method to apply option specific validation.
* Options can be written to as normal.
*
* The option variable names correspond with option strings that can be passed
* through luminous::set(), however, for historical reasons, underscores can be
* replaced with dashed in the call.
*/
class LuminousOptions {
/**
* @brief Whether to use the built-in cache
*/
private $cache = true;
/**
* @brief Maximum age of cache files in seconds
*
* Cache files which have not been read for this length of time will be
* removed from the file system. The file's 'mtime' is used to calculate
* when it was last used, and a cache hit triggers a 'touch'
*
* Set to -1 or 0 to disable cache purges
*/
private $cache_age = 7776000; // 90 days
/**
* @brief Word wrapping
*
* If the formatter supports line wrapping, lines will be wrapped at
* this number of characters (0 or -1 to disable)
*/
private $wrap_width = -1;
/**
* @brief Line numbering
*
* If the formatter supports line numbering, this setting controls whether
* or not lines should be numbered
*/
private $line_numbers = true;
/**
* @brief Line number of first line
*
* If the formatter supports line numbering, this setting controls number
* of the first line
*/
private $start_line = 1;
/**
* @brief Highlighting of lines
*
* If the formatter supports highlighting lines, this setting allows
* the caller to specify the set of line numbers to highlight
*/
private $highlight_lines = array();
/**
* @brief Hyperlinking
*
* If the formatter supports hyper-linking, this setting controls whether
* or not URLs will be automatically linked
*/
private $auto_link = true;
/**
* @brief Widget height constraint
*
* If the formatter supports heigh constraint, this setting controls whether
* or not to constrain the widget's height, and to what.
*/
private $max_height = -1;
/**
* @brief Output format
*
* Chooses which output format to use. Current valid settings are:
* \li 'html' - standard HTML element, contained in a \<div\> with class 'luminous',
* CSS is not included and must be included on the page separately
* (probably with luminous::head_html())
* \li 'html-full' - A complete HTML document. CSS is included.
* \li 'html-inline' - Very similar to 'html' but geared towards inline display.
* Probably not very useful.
* \li 'latex' - A full LaTeX document
* \li 'none' or \c NULL - No formatter. Internal XML format is returned.
* You probably don't want this.
*/
private $format = 'html';
/**
* @brief Theme
*
* The default theme to use. This is observed by the HTML-full and LaTeX
* formatters, it is also read by luminous::head_html().
*
* This should be a valid theme which exists in style/
*/
private $theme = 'luminous_light.css';
/**
* @brief HTML strict standards mode
*
* The HTML4-strict doctype disallows a few things which are technically
* useful. Set this to true if you don't want Luminous to break validation
* on your HTML4-strict document. Luminous should be valid
* HTML4 loose/transitional and HTML5 without needing to enable this.
*/
private $html_strict = false;
/**
* @brief Location of Luminous relative to your document root
*
* If you use luminous::head_html(), it has to try to figure out the
* path to the style/ directory so that it can return a correct URL to the
* necessary stylesheets. Luminous may get this wrong in some situations,
* specifically it is currently impossible to get this right if Luminous
* exists on the filesystem outside of the document root, and you have used
* a symbolic link to put it inside. For this reason, this setting allows you
* to override the path.
*
* e.g. If you set this to '/extern/highlighter', the stylesheets will be
* linked with
* \<link rel='stylesheet' href='/extern/highlighter/style/luminous.css'\>
*
*/
private $relative_root = null;
/**
* @brief JavaScript extras
*
* controls whether luminous::head_html() outputs the javascript 'extras'.
*/
private $include_javascript = false;
/**
* @brief jQuery
*
* Controls whether luminous::head_html() outputs jQuery, which is required
* for the JavaScript extras. This has no effect if $include_javascript is
* false.
*/
private $include_jquery = false;
/**
* @brief Failure recovery
*
* If Luminous hits some kind of unrecoverable internal error, it should
* return the input source code back to you. If you want, it can be
* wrapped in an HTML tag. Hopefully you will never see this.
*/
private $failure_tag = 'pre';
/**
* @brief Defines an SQL function which can execute queries on a database
*
* An SQL database can be used as a replacement for the file-system cache
* database.
* This function should act similarly to the mysql_query function:
* it should take a single argument (the query string) and return:
* @li boolean @c false if the query fails
* @li boolean @c true if the query succeeds but has no return value
* @li An array of associative arrays if the query returns rows (each
* element is a row, and each row is an map keyed by field name)
*/
private $sql_function = null;
private $verbose = true;
public function LuminousOptions($opts=null) {
if (is_array($opts)) {
$this->set($opts);
}
}
public function set($nameOrArray, $value=null) {
$array = $nameOrArray;
if (!is_array($array)) {
$array = array($nameOrArray => $value);
}
foreach($array as $option => $value) {
// for backwards compatibility we need to do this here
$option = str_replace('-', '_', $option);
$this->__set($option, $value);
}
}
private static function check_type($value, $type, $nullable=false) {
if ($nullable && $value === null) return true;
$func = null;
if ($type === 'string') $func = 'is_string';
elseif($type === 'int') $func = 'is_int';
elseif($type === 'numeric') $func = 'is_numeric';
elseif($type === 'bool') $func = 'is_bool';
elseif($type === 'func') $func = 'is_callable';
elseif($type === 'array') $func = 'is_array';
else {
assert(0);
return true;
}
$test = call_user_func($func, $value);
if (!$test) {
throw new InvalidArgumentException('Argument should be type ' . $type .
($nullable? ' or null' : ''));
}
return $test;
}
public function __get($name) {
if (property_exists($this, $name))
return $this->$name;
else {
throw new Exception('Unknown property: ' . $name);
}
}
public function __set($name, $value) {
if ($name === 'auto_link')
$this->set_bool($name, $value);
else if ($name === 'cache') {
$this->set_bool($name, $value);
}
elseif($name === 'cache_age') {
if (self::check_type($value, 'int')) $this->$name = $value;
}
elseif($name === 'failure_tag') {
if (self::check_type($value, 'string', true)) $this->$name = $value;
}
elseif($name === 'format')
$this->set_format($value);
elseif($name === 'html_strict') {
if (self::check_type($value, 'bool')) $this->$name = $value;
}
elseif($name === 'include_javascript' || $name === 'include_jquery')
$this->set_bool($name, $value);
elseif($name === 'line_numbers')
$this->set_bool($name, $value);
elseif($name === 'start_line')
$this->set_start_line($value);
elseif($name === 'highlight_lines') {
if (self::check_type($value, 'array'))
$this->highlight_lines = $value;
}
elseif($name === 'max_height')
$this->set_height($value);
elseif($name === 'relative_root') {
if (self::check_type($value, 'string', true)) $this->$name = $value;
}
elseif($name === 'theme')
$this->set_theme($value);
elseif($name === 'wrap_width') {
if (self::check_type($value, 'int')) $this->$name = $value;
}
elseif($name === 'sql_function') {
if (self::check_type($value, 'func', true)) $this->$name = $value;
}
elseif ($name === 'verbose') {
$this->set_bool($name, $value);
}
else {
throw new Exception('Unknown option: ' . $name);
}
}
private function set_bool($key, $value) {
if (self::check_type($value, 'bool')) {
$this->$key = $value;
}
}
private function set_string($key, $value, $nullable=false) {
if (self::check_type($value, 'string', $nullable)) {
$this->$key = $value;
}
}
private function set_start_line($value) {
if (is_numeric($value) && $value > 0) {
$this->start_line = $value;
} else {
throw new InvalidArgumentException('Start line must be a positive number');
}
}
private function set_format($value) {
// formatter can either be an instance or an identifier (string)
$is_obj = $value instanceof LuminousFormatter;
if($is_obj || self::check_type($value, 'string', true)) {
// validate the string is a known type
if (!$is_obj && !in_array($value, array('html', 'html-full',
'html-inline', 'latex', 'none', null), true)) {
throw new Exception('Invalid formatter: ' . $value);
}
else {
$this->format = $value;
}
}
}
private function set_theme($value) {
if (self::check_type($value, 'string')) {
if (!preg_match('/\.css$/', $value)) $value .= '.css';
if (!luminous::theme_exists($value)) {
throw new Exception('No such theme: '
. luminous::root() . '/style/' . $value);
}
else $this->theme = $value;
}
}
private function set_height($value) {
// height should be either a number or a numeric string with some units at
// the end
if (is_numeric($value)
|| (is_string($value) && preg_match('/^\d+/', $value))
) {
$this->max_height = $value;
}
else {
throw new InvalidArgumentException('Unrecognised format for height');
}
}
}
/// @endcond

View file

@ -0,0 +1,221 @@
<?php
/// \cond ALL
/**
* \file scanners.class.php
* \brief Scanner lookup table definition.
*/
/**
* \class LuminousScanners
* \author Mark Watkinson
* \brief A glorified lookup table for languages to scanners.
* One of these is instantiated in the global scope at the bottom of this source.
* The parser assumes it to exist and uses it to look up scanners.
* Users seeking to override scanners or add new scanners should add their
* scanner into '$luminous_scanners'.
*
*/
class LuminousScanners
{
private $lookup_table = array(); /**<
The language=>scanner lookup table. Scanner is an array with keys:
scanner (the string of the scanner's class name),
file (the path to the file in which its definition resides)
dependencies (the language name for any scanners it this scanner
either depends on or needs to instantiate itself)
*/
private $default_scanner = null; /**<
Language name of the default scanner to use if none is found
for a particular language */
private $descriptions = array();
private $resolved_dependencies = array();
/**
* Adds a scanner into the table, or overwrites an existing scanner.
*
* \param language_name may be either a string or an array of strings, if
* multiple languages are to use the same scanner
* \param $scanner the name of the LuminousScanner object as string, (not an
* actual instance!). If the file is actually a dummy file (say for includes),
* leave $scanner as \c null.
* \param lang_description a human-readable description of the language.
* \param file the path to the file in which the scanner is defined.
* \param dependencies optional, a string or array of strings representing
* the language names (given in another call to AddScanner, as
* language_name), on which the instantiation of this scanner depends.
* i.e. any super-classes, and any classes which this scanner instantiates
* itself.
*
*/
public function AddScanner($language_name, $scanner,
$lang_description, $file=null, $dependencies=null)
{
$dummy = $scanner === null;
$d = array();
if (is_array($dependencies))
$d = $dependencies;
elseif ($dependencies !== null)
$d = array($dependencies);
$insert = array('scanner'=>$scanner,
'file'=>$file,
'dependencies'=>$d,
'description'=>$lang_description
);
if (!is_array($language_name))
$language_name = array($language_name);
foreach($language_name as $l) {
$this->lookup_table[$l] = $insert;
if (!$dummy)
$this->AddDescription($lang_description, $l);
}
}
private function AddDescription($language_name, $language_code)
{
if (!isset($this->descriptions[$language_name]))
$this->descriptions[$language_name] = array();
$this->descriptions[$language_name][] = $language_code;
}
private function UnsetDescription($language_name)
{
foreach($this->descriptions as &$d)
{
foreach($d as $k=>$l)
{
if($l === $language_name)
unset($d[$k]);
}
}
}
/**
* Removes a scanner from the table
*
* \param language_name may be either a string or an array of strings, each of
* which will be removed from the lookup table.
*/
public function RemoveScanner($language_name)
{
if (is_array($language_name))
{
foreach($language_name as $l)
{
unset($this->lookup_table[$l]);
$this->UnsetDescription($l);
}
}
else
{
$this->UnsetDescription($language_name);
unset($this->lookup_table[$language_name]);
}
}
/**
* Sets the default scanner. This is used when none matches a lookup
* \param scanner the LuminousScanner object
*/
public function SetDefaultScanner($scanner)
{
$this->default_scanner = $scanner;
}
/**
* Method which retrives the desired scanner array, and
* recursively settles the include dependencies while doing so.
* \param language_name the name under which the gramar was originally indexed
* \param default if true: if the scanner doesn't exist, return the default
* scanner. If false, return false
* \return the scanner-array stored for the given language name
* \internal
* \see LuminousScanners::GetScanner
*/
private function GetScannerArray($language_name, $default=true)
{
$g = null;
if (array_key_exists($language_name, $this->lookup_table))
$g = $this->lookup_table[$language_name];
elseif($this->default_scanner !== null && $default === true)
$g = $this->lookup_table[$this->default_scanner];
if ($g === null)
return false;
// Break on circular dependencies.
if (!isset($this->resolved_dependencies[$language_name]))
{
$this->resolved_dependencies[$language_name] = true;
foreach($g['dependencies'] as $d)
{
$this->GetScannerArray($d, $default);
}
if ($g['file'] !== null)
require_once($g['file']);
}
return $g;
}
/**
* Returns a scanner for a language
* \param language_name the name under which the gramar was originally indexed
* \param default if true: if the scanner doesn't exist, return the default
* scanner. If false, return false
* \return The scanner, the default scanner, or null.
*/
function GetScanner($language_name, $default=true, $instance=true)
{
$resolved_dependencies = array();
$g = $this->GetScannerArray($language_name, $default);
$resolved_dependencies = array();
if ($g !== false) {
return $instance? new $g['scanner'] : $g['scanner'];
}
return null;
}
function GetDescription($language_name) {
$resolved_dependencies = array();
$g = $this->GetScannerArray($language_name, true);
$resolved_dependencies = array();
if ($g !== false) {
return $g['description'];
} else return null;
}
/**
* Returns a list of known aliases for scanners.
* \return a list, the list is such that each item is itself a list whose
* elements are aliases of the same scanner. eg:
* [
* ['c', 'cpp'],
* ['java'],
* ['py', 'python']
* ]
* etc.
*
*/
function ListScanners()
{
$l = $this->descriptions;
return $l;
}
}
/// \endcond

View file

@ -0,0 +1,386 @@
<?php
/// \cond ALL
/*
* This is a simple CSS parser, which we use to make CSS themes portable.
* The basic idea is we're going to use the CSS scanner to tokenize the
* input, then we're going to parse the tokens.
* There is some amount of redundancy here with the scanner, but this way
* means that we are 1) not dependent too much on the implementation of
* the scanner, and 2) not having to write our own with full pattern matching.
*
*/
require_once dirname(__FILE__) . '/../luminous.php';
// source: http://www.w3schools.com/css/css_colornames.asp
global $luminous_col2hex;
$luminous_col2hex = array(
'aliceblue' => '#f0f8ff',
'antiquewhite' => '#faebd7',
'aqua' => '#00ffff',
'aquamarine' => '#7fffd4',
'azure' => '#f0ffff',
'beige' => '#f5f5dc',
'bisque' => '#ffe4c4',
'black' => '#000000',
'blanchedalmond' => '#ffebcd',
'blue' => '#0000ff',
'blueviolet' => '#8a2be2',
'brown' => '#a52a2a',
'burlywood' => '#deb887',
'cadetblue' => '#5f9ea0',
'chartreuse' => '#7fff00',
'chocolate' => '#d2691e',
'coral' => '#ff7f50',
'cornflowerblue' => '#6495ed',
'cornsilk' => '#fff8dc',
'crimson' => '#dc143c',
'cyan' => '#00ffff',
'darkblue' => '#00008b',
'darkcyan' => '#008b8b',
'darkgoldenrod' => '#b8860b',
'darkgray' => '#a9a9a9',
'darkgrey' => '#a9a9a9',
'darkgreen' => '#006400',
'darkkhaki' => '#bdb76b',
'darkmagenta' => '#8b008b',
'darkolivegreen' => '#556b2f',
'darkorange' => '#ff8c00',
'darkorchid' => '#9932cc',
'darkred' => '#8b0000',
'darksalmon' => '#e9967a',
'darkseagreen' => '#8fbc8f',
'darkslateblue' => '#483d8b',
'darkslategray' => '#2f4f4f',
'darkslategrey' => '#2f4f4f',
'darkturquoise' => '#00ced1',
'darkviolet' => '#9400d3',
'deeppink' => '#ff1493',
'deepskyblue' => '#00bfff',
'dimgray' => '#696969',
'dimgrey' => '#696969',
'dodgerblue' => '#1e90ff',
'firebrick' => '#b22222',
'floralwhite' => '#fffaf0',
'forestgreen' => '#228b22',
'fuchsia' => '#ff00ff',
'gainsboro' => '#dcdcdc',
'ghostwhite' => '#f8f8ff',
'gold' => '#ffd700',
'goldenrod' => '#daa520',
'gray' => '#808080',
'grey' => '#808080',
'green' => '#008000',
'greenyellow' => '#adff2f',
'honeydew' => '#f0fff0',
'hotpink' => '#ff69b4',
'indianred' => '#cd5c5c',
'indigo' => '#4b0082',
'ivory' => '#fffff0',
'khaki' => '#f0e68c',
'lavender' => '#e6e6fa',
'lavenderblush' => '#fff0f5',
'lawngreen' => '#7cfc00',
'lemonchiffon' => '#fffacd',
'lightblue' => '#add8e6',
'lightcoral' => '#f08080',
'lightcyan' => '#e0ffff',
'lightgoldenrodyellow' => '#fafad2',
'lightgray' => '#d3d3d3',
'lightgrey' => '#d3d3d3',
'lightgreen' => '#90ee90',
'lightpink' => '#ffb6c1',
'lightsalmon' => '#ffa07a',
'lightseagreen' => '#20b2aa',
'lightskyblue' => '#87cefa',
'lightslategray' => '#778899',
'lightslategrey' => '#778899',
'lightsteelblue' => '#b0c4de',
'lightyellow' => '#ffffe0',
'lime' => '#00ff00',
'limegreen' => '#32cd32',
'linen' => '#faf0e6',
'magenta' => '#ff00ff',
'maroon' => '#800000',
'mediumaquamarine' => '#66cdaa',
'mediumblue' => '#0000cd',
'mediumorchid' => '#ba55d3',
'mediumpurple' => '#9370d8',
'mediumseagreen' => '#3cb371',
'mediumslateblue' => '#7b68ee',
'mediumspringgreen' => '#00fa9a',
'mediumturquoise' => '#48d1cc',
'mediumvioletred' => '#c71585',
'midnightblue' => '#191970',
'mintcream' => '#f5fffa',
'mistyrose' => '#ffe4e1',
'moccasin' => '#ffe4b5',
'navajowhite' => '#ffdead',
'navy' => '#000080',
'oldlace' => '#fdf5e6',
'olive' => '#808000',
'olivedrab' => '#6b8e23',
'orange' => '#ffa500',
'orangered' => '#ff4500',
'orchid' => '#da70d6',
'palegoldenrod' => '#eee8aa',
'palegreen' => '#98fb98',
'paleturquoise' => '#afeeee',
'palevioletred' => '#d87093',
'papayawhip' => '#ffefd5',
'peachpuff' => '#ffdab9',
'peru' => '#cd853f',
'pink' => '#ffc0cb',
'plum' => '#dda0dd',
'powderblue' => '#b0e0e6',
'purple' => '#800080',
'red' => '#ff0000',
'rosybrown' => '#bc8f8f',
'royalblue' => '#4169e1',
'saddlebrown' => '#8b4513',
'salmon' => '#fa8072',
'sandybrown' => '#f4a460',
'seagreen' => '#2e8b57',
'seashell' => '#fff5ee',
'sienna' => '#a0522d',
'silver' => '#c0c0c0',
'skyblue' => '#87ceeb',
'slateblue' => '#6a5acd',
'slategray' => '#708090',
'slategrey' => '#708090',
'snow' => '#fffafa',
'springgreen' => '#00ff7f',
'steelblue' => '#4682b4',
'tan' => '#d2b48c',
'teal' => '#008080',
'thistle' => '#d8bfd8',
'tomato' => '#ff6347',
'turquoise' => '#40e0d0',
'violet' => '#ee82ee',
'wheat' => '#f5deb3',
'white' => '#ffffff',
'whitesmoke' => '#f5f5f5',
'yellow' => '#ffff00',
'yellowgreen' => '#9acd32'
);
/**
* @brief Simple CSS parser to make theme files portable across output formats.
*
* This is CSS parser for making Luminous themes portable. This is not
* a general CSS parser, but could be with a bit of work!
*
* Parses CSS strings into a map of rules and values. The resulting map is
* a somewhat simplified version of CSS.
* For simplificity we re-map the following properties:
*
* background-color => bgcolor
* font-weight => bold? (bool)
* font-style => italic? (bool)
* text-decoration => underline? OR strikethrough? (bool)
*
* We also drop things like '!important'.
*
* Colours are stored as 6-digit hexadecimal strings with leading #. 3-digit
* hex strings are expanded to their 6-digit equivalents. Named colour aliases
* are replaced with their hex equivalents.
*/
class LuminousCSSParser {
private $data = array();
private static function format_property_value($prop, $value) {
global $luminous_col2hex;
// drop !important
$value = preg_replace('/\s*!important$/', '', $value);
// expand 3-digit hex
if (preg_match('/^#([a-fA-F0-9]{3})$/', $value, $m))
$value .= $m[1];
// remove quotes
$value = trim($value);
if (preg_match('/^(["\'])(.*)\\1$/', $value, $m)) $value = $m[2];
// now get it into a simpler form:
switch($prop) {
case 'color':
if (isset($luminous_col2hex[$value]))
$value = $luminous_col2hex[$value];
break;
case 'background-color':
$prop = 'bgcolor';
if (isset($luminous_col2hex[$value]))
$value = $luminous_col2hex[$value];
break;
case 'font-weight':
$prop = 'bold';
$value = in_array($value, array('bold', 'bolder', '700', '800', '900'));
break;
case 'font-style':
$prop = 'italic';
$value = in_array($value, array('italic', 'oblique'));
case 'text-decoration':
if ($value === 'line-through') {
$prop = 'strikethrough';
$value = true;
}
elseif($value === 'underline') {
$prop = 'underline';
$value = true;
}
break;
}
return array($prop, $value);
}
private static function format_css_array($css) {
$css_ = array();
// now cleanup the array, drop !important
foreach($css as $selector=>$rules) {
$rules_ = array();
foreach($rules as $prop=>$value) {
list($prop, $value) = self::format_property_value($prop, $value);
$rules_[$prop] = $value;
}
// now split selector by comma
$selectors = preg_split('/\s*,\s*/', $selector);
foreach($selectors as $s) {
// drop .luminous from the selector
$s = preg_replace('/^\.luminous\s*/', '', $s);
// now we assume that if something is in the form .classname then
// it's probably of interest, ie directly specifying a rule for a
// highlighting calss.
// and if it's not in that form them
// it's probably something else (like a rule for hyperlinks or something)
if (preg_match('/\.([\-\w]+)/', $s, $m)) $s = $m[1];
else continue;
if (!isset($css_[$s])) $css_[$s] = array();
$css_[$s] = array_merge($css_[$s], $rules_);
}
}
return $css_;
}
/**
* Returns the parsed rules. The rules are an array in the format:
*
* array(
* $rule_name => array($property => $value)
* )
*
* So, $rules['.comment']['color'] would return the color property of comment
* classes.
*/
function rules() {
return $this->data;
}
/**
* Returns the value for the given property of the given rule name, or
* returns $default.
* @param $rule_name the CSS rule name, e.g. 'a', '.luminous', etc
* @param $property the property to access, e.g. 'color'
* @param $default the value to return in the case that the rule/proeprty
* was not set. Default: null
*/
function value($rule_name, $property, $default=null) {
if (isset($this->data[$rule_name][$property]))
return $this->data[$rule_name][$property];
else return $default;
}
/**
* Converts a CSS string into a nested map of values.
* See LuminousCSSParser::rules() for the structure.
* @param $string the CSS string
* @returns the rule map
*/
function convert($string) {
$css = self::parse($string);
$data = self::format_css_array($css);
$this->data = $data;
return $data;
}
private static function parse($string) {
global $luminous_col2hex;
// singleton from usage API
global $luminous_;
$scanner = $luminous_->scanners->GetScanner('css');
$scanner->string($string);
$scanner->init();
$scanner->main();
$tokens = $scanner->token_array();
$block = false;
$expect;
$key;
$value;
$selector = '';
$css = array();
// array of selectors => rules, where rules is an array itself of (property, value)
// note this is going to get @font-face wrong, but we don't care about that.
for($i=0; $i<count($tokens); $i++) {
list($tok, $content, ) = $tokens[$i];
if ($tok === 'COMMENT') continue;
if (!$block) {
$expect = 'key';
// not in block, look for selectors.
if ($content === '{') {
$block = true;
$key = '';
$selector = trim($selector);
if (!isset($css[$selector]))
$css[$selector] = array();
}
else {
$selector .= $content;
}
continue;
}
if ($content === '}') {
$block = false;
$value = null;
$key = null;
$selector = '';
continue;
}
// expecting key, append to the key or finalise it
if ($expect === 'key') {
if ($content === ':') {
$expect = 'value';
$value = '';
}
else $key .= $content;
} elseif($expect === 'value') {
// expecting value, append to it or finalise and insert it (with the key)
if ($content === ';') {
// don't overwrite things - use the first definition
// this is for stuff like rgba which might re-define something
$key = trim($key);
$value = trim($value);
if (!isset($css[$selector][$key])) {
$css[$selector][$key] = $value;
}
$expect = 'key';
$key = '';
$value = '';
}
else $value .= $content;
}
}
return $css;
}
}
/// \endcond

View file

@ -0,0 +1 @@
allow from all

View file

@ -0,0 +1,73 @@
.luminous {
background-color: #EEEEED;
}
/* line numbers */
.luminous .line-numbers {
background-color: #F0F0F0 !important;
border-right: 1px dashed #BBBBBB !important;
color: #555555;
}
.luminous .code .highlight {
background-color: #A4C0E4;
background-color: rgba(164, 192, 228, 0.5);
}
.luminous .character { color: #9C0F0F !important; }
.luminous .comment { color: #57401E !important; font-style: italic !important;}
.luminous .comment_note { font-weight: bold !important; font-style:normal !important;}
.luminous .constant { color : #462886 !important; }
.luminous .delimiter {color: #CF4913 !important; font-weight:bold !important; }
.luminous .diff_header_new{color: #008C00 !important;}
.luminous .diff_header_old{color: #BF0303 !important;}
.luminous .diff_range{color:#0000FF !important;}
.luminous .doccomment{color: #00734D !important;}
.luminous .doctag {color: #00BF00 !important; font-weight: bold !important;}
.luminous .docproperty{font-weight: bold !important;}
.luminous .esc{color: #8F6B32 !important;}
.luminous .function{ color: #E20800 !important;}
.luminous .heredoc, .luminous .string {
color: #00892C !important;
background-color: rgba(119, 183, 83, 0.15) !important;
}
.luminous .htmltag {
font-weight: bold !important;
color: #644A9B !important;
}
.luminous .interpolation {background-color: rgba(164, 192, 228, 0.15) !important;}
.luminous .keyword { font-weight:bold !important; color: #2C72C7 !important; }
.luminous .numeric {color: #00BF00 !important; }
.luminous .operator {color: #E20071 !important; }
.luminous .obj {}
.luminous .oo { color: #00A7B3 !important; }
.luminous .preprocessor { color: #004D00 !important; }
.luminous .regex {color: #644A9B !important; }
.luminous .type { color: #730055 !important; }
.luminous .user_function {font-weight: bold !important;}
.luminous .value{ color: #555753 !important; }
.luminous .variable { color: #00438A !important; }
/* this stuff has to come after the other rules because it will probably
* override some of them
*/
.luminous .diff_old{
background-color:#FFBFBF !important;
background-color: rgba(232, 87, 82, 0.15) !important;
}
.luminous .diff_new{
background-color:#BFFFBF !important;
background-color: rgba(0, 191, 0, 0.15) !important;
}
.luminous .diff_unchanged, .luminous .diff_unchanged * {
color: #888A85 !important;
background-color: transparent !important;
}

View file

@ -0,0 +1,146 @@
.luminous {
color:black !important;
border-color: #ddd;
background-color: #FFFFFF;
}
.luminous .highlight {
background-color:#FFFFCC;
}
/* line numbers */
.luminous .line-numbers {
background-color:#ECECEC !important;
color: #aaa !important;
border-right:1px solid #ddd !important;
}
.luminous .code {
}
.luminous a.link, .luminous a.link:visited {
color:blue !important;
}
.luminous .character{color:#DD1144 !important;}
.luminous .comment{color:#999988 !important; font-style:italic !important;}
/* NOTE: TODO: etc. */
.luminous .comment_note{
background-color:#FFB2B0 !important;
background-color:rgba(255, 178, 176, 0.70) !important;
color:black !important;
font-style:normal !important;}
.luminous .constant{}
.luminous .esc{color:#057CA3 !important;}
.luminous .function{color:#0086B3 !important;}
.luminous .heredoc{color:#DD1144 !important; }
.luminous .htmltag{color:#000080 !important;}
.luminous .keyword{font-weight:bold !important;}
.luminous .numeric {color:#009999 !important;}
.luminous .operator{font-weight: bold !important;}
.luminous .oo{/*color:#339900 !important;*/}
.luminous .obj{color:#3366FF !important;}
.luminous .preprocessor{color:#999999 !important; font-weight: bold !important;}
.luminous .shebang{font-weight:bold !important; font-style:normal !important; color:purple !important;}
.luminous .string{color:#DD1144 !important; }
.luminous .type{color:#445588 !important; font-weight: bold;}
/* not used*/
.luminous .user_function{color: #990000 !important; font-weight:bold !important;}
.luminous .value{color:#999999 !important;}
.luminous .variable{color:#008080 !important;}
.luminous .doccomment{color:blue !important;}
.luminous .doctag {color:#CA60CA !important;}
.luminous .docstr {color:red !important;}
.luminous .docproperty{color:#0095FF !important; font-weight:bold !important;}
.luminous .lang_latex{color:blue !important;}
.luminous .latex_function{color:red !important; font-weight:normal !important;}
.luminous .latex_operator{color:black !important;}
.luminous .make_target{color:magenta !important;}
.luminous .make_dep{color: blue !important;}
.luminous .regex{color:#8000FF !important;
background-color:#F9F3FF !important;
background-color:rgba(231, 206, 255, 0.20) !important;
}
.luminous .regex_subpattern{font-weight:bold !important; color:black !important;}
.luminous .regex_class_marker{color:#0083E1 !important; font-weight:bold !important;}
.luminous .regex_subpattern_marker{color:green !important; font-weight:bold !important;}
.luminous .regex_repeat_marker {color:blue !important;}
.luminous .regex_operator{color:green !important;}
/* Specific to the whitespace language */
.luminous .whitespace_space{background-color:#FF5151 !important;}
.luminous .whitespace_tab{background-color:#A1B6FF !important;}
.luminous .diff_header_new{color:green !important;}
.luminous .diff_header_old{color:red !important;}
.luminous .diff_range{color:blue !important;}
.luminous .diff_old{background-color:#FFBFBF !important;
background-color: rgba(255, 191, 191, 0.5) !important;
}
.luminous .diff_new{background-color:#BFFFBF !important;
background-color: rgba(191, 255, 191, 0.5) !important;
}
.luminous .diff_unchanged{
color: #888888 !important;
}
.luminous .diff_unchanged * {
background-color: transparent !important;
color: #888888 !important;
}
.luminous .xml {
color: #666 !important;
}
.luminous .xml * {
color: black !important;
}
.luminous .xml .type {
color: #00892C !important;
font-weight: normal !important;
}
.luminous .xml .htmltag {
color: #2C72C7 !important;
}
.luminous .xml .string {
color: #DD1144 !important;
}
.luminous .xml .comment {
color: gray !important;
}
.luminous .interpolation * {
background-color: transparent !important;
}
.luminous .interpolation {
font-style: italic;
/* background-color: rgba(30, 80, 200, 0.1); */
}
.luminous .delimiter {
font-weight:bold;
color: #999999;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 823 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

View file

@ -0,0 +1,117 @@
/**
* Kimono - heavily based on the Monokai theme.
*
* Dark, simple and uncluttered.
*
* Some colours stolen from http://www.eclipsecolorthemes.org/?view=theme&id=52
*/
.luminous {
color : #F8F8F2;
border-color: #555;
background-color: #272822;
}
.luminous .code {
}
/* line numbers */
.luminous .line-numbers {
border-right: 1px solid #d0d0d0 !important;
background-color: #272822 !important;
color : #F8F8F2 !important;
}
.luminous .highlight {
background-color: #3E3D32 !important;
}
.luminous .comment {
color: #75715E !important;
}
.luminous .preprocessor {
color: #9C977D !important;
}
.luminous .comment_note {
color: #CCDF32 !important;
}
.luminous .doccomment {
color: #D9E577 !important;
}
.luminous .doctag {
font-weight: bold !important;
}
.luminous .operator {
color: #F95994 !important;
}
.luminous .numeric {
color: #7FB347 !important;
}
.luminous .string, .luminous .character, .luminous .heredoc, .luminous .regex {
color: #E6DB74 !important;
}
.luminous .keyword {
color: #66CCB3 !important;
}
.luminous .variable {
color: #79ABFF !important;
}
.luminous .constant {
color: #EFB571 !important;
}
.luminous .function {
color: #79ABFF !important;
}
.luminous .user_function {
color: #BED6FF !important;
}
.luminous .htmltag {
color: #BED6FF !important;
}
.luminous .type {
color: #BFA4A4 !important;
}
.luminous .delimiter {
font-weight: bold !important;
}
.luminous .esc {
color: #AE81FF !important;
}
.luminous .diff_header_old {
color: #D25252 !important;
font-weight: bold !important;
}
.luminous .diff_header_new {
color: #51D251 !important;
font-weight: bold !important;
}
.luminous .diff_old {
color: #D25252 !important;
background-color: #2A0000 !important;
}
.luminous .diff_new {
color: #51D251 !important;
background-color: #002A00 !important;
}
.luminous .diff_unchanged{
color: #888888 !important;
}
.luminous .diff_unchanged * {
background-color: transparent !important;
color: #888888 !important;
}
.luminous .diff_range {
color: #79ABFF !important;
}
.luminous a {
color: #F95994 !important;
}

View file

@ -0,0 +1,134 @@
.luminous {
font-size: inherit;
text-align: left;
font-family: "Consolas", "DejaVu Sans Mono", monospace;
overflow: auto;
border: 2px solid #dddddd;
border-radius: 5px;
position: relative;
/* line numbers */ }
.luminous.inline {
min-width: 1px;
max-width: none;
line-height: normal; }
.luminous.inline, .luminous.inline div, .luminous.inline pre {
padding: 0;
*display: inline;
display: inline-block; }
.luminous.inline pre {
overflow: visible; }
.luminous .link, .luminous .link:visited {
text-decoration: underline; }
.luminous .link:hover {
text-decoration: none; }
.luminous pre {
margin: 0;
padding: 0;
background: transparent none;
font-size: inherit;
font-family: inherit;
width: auto; }
.luminous > pre {
padding-left: 1em; }
.luminous table {
width: 100%;
vertical-align: top;
border-collapse: collapse;
border-spacing: 0; }
.luminous table td {
padding: 0;
margin: 0;
vertical-align: top; }
.luminous table td.code-container {
width: 100%; }
.luminous table .code {
transition-property: padding-left;
transition-duration: 0.2s;
transition-timing-function: ease-in;
-webkit-transition-property: padding-left;
-webkit-transition-duration: 0.2s;
-webkit-transition-timing-function: ease-in;
-moz-transition-property: padding-left;
-moz-transition-duration: 0.2s;
-moz-transition-timing-function: ease-in;
-o-transition-property: padding-left;
-o-transition-duration: 0.2s;
-o-transition-timing-function: ease-in;
-ms-transition-property: padding-left;
-ms-transition-duration: 0.2s;
-ms-transition-timing-function: ease-in; }
.luminous table .code > span {
padding: 0 0 0 1em;
display: block;
transition-property: background-color;
transition-duration: 0.5s;
transition-timing-function: ease-in;
-webkit-transition-property: background-color;
-webkit-transition-duration: 0.5s;
-webkit-transition-timing-function: ease-in;
-moz-transition-property: background-color;
-moz-transition-duration: 0.5s;
-moz-transition-timing-function: ease-in;
-o-transition-property: background-color;
-o-transition-duration: 0.5s;
-o-transition-timing-function: ease-in;
-ms-transition-property: background-color;
-ms-transition-duration: 0.5s;
-ms-transition-timing-function: ease-in; }
.luminous table .line-numbers {
cursor: default;
text-align: center;
overflow: hidden;
max-width: 60px;
transition-property: max-width;
transition-duration: 0.2s;
transition-timing-function: ease-in;
-webkit-transition-property: max-width;
-webkit-transition-duration: 0.2s;
-webkit-transition-timing-function: ease-in;
-moz-transition-property: max-width;
-moz-transition-duration: 0.2s;
-moz-transition-timing-function: ease-in;
-o-transition-property: max-width;
-o-transition-duration: 0.2s;
-o-transition-timing-function: ease-in;
-ms-transition-property: max-width;
-ms-transition-duration: 0.2s;
-ms-transition-timing-function: ease-in; }
.luminous table .line-numbers span {
padding: 0 0.5em;
display: block; }
.luminous table .line-numbers span:nth-child(5n) {
font-weight: bold; }
.luminous table .line-numbers.collapsed {
border: none;
max-width: 0; }
.luminous div {
border-radius: inherit; }
.luminous .line-number-control {
transition-property: left;
transition-duration: 0.2s;
transition-timing-function: ease-in;
-webkit-transition-property: left;
-webkit-transition-duration: 0.2s;
-webkit-transition-timing-function: ease-in;
-moz-transition-property: left;
-moz-transition-duration: 0.2s;
-moz-transition-timing-function: ease-in;
-o-transition-property: left;
-o-transition-duration: 0.2s;
-o-transition-timing-function: ease-in;
-ms-transition-property: left;
-ms-transition-duration: 0.2s;
-ms-transition-timing-function: ease-in;
height: 26px;
width: 18px;
display: block;
position: absolute;
top: 0;
right: 0;
z-index: 1;
cursor: pointer;
background: transparent url(icons/chevron-left.png); }
.luminous .line-number-control.show-line-numbers {
background: transparent url(icons/chevron-right.png); }

View file

@ -0,0 +1,138 @@
// This is the source file for luminous.css
// If you want to edit it, you'll need SASS.
// http://sass-lang.com/
@mixin vendor-prefix($property, $value) {
@each $prefix in '', -webkit-, -moz-, -o-, -ms- {
#{$prefix}#{$property} : $value;
}
}
@mixin transition($properties, $duration: 0.5s, $easing: ease-in) {
@each $prefix in '', -webkit-, -moz-, -o-, -ms- {
#{$prefix}transition-property: $properties;
#{$prefix}transition-duration : $duration;
#{$prefix}transition-timing-function: $easing;
}
}
.luminous {
font-size:inherit;
text-align:left;
font-family: "Consolas", "DejaVu Sans Mono", monospace;
overflow: auto;
border: 2px solid #ddd;
border-radius: 5px;
position: relative;
&.inline {
min-width: 1px;
max-width: none;
line-height: normal;
&, div, pre {
padding: 0;
*display: inline;
display: inline-block;
}
pre {
// this would otherwise get 'overflow auto', but auto seems to
// cause it to sit a bit out of line with the text. So does 'hidden'.
// in any case, this shouldn't overflow.
overflow: visible;
}
}
.link {
&, &:visited {
text-decoration: underline;
}
&:hover {
text-decoration: none;
}
}
pre {
margin: 0;
padding: 0;
background: transparent none;
font-size: inherit;
font-family: inherit;
width: auto;
}
// non-line numbered, directly inside the .luminous
& > pre {
padding-left: 1em;
}
/* line numbers */
table {
width: 100%;
vertical-align: top;
border-collapse: collapse;
border-spacing: 0;
td {
padding: 0;
margin: 0;
vertical-align: top;
&.code-container { width: 100%; }
}
.code {
& > span {
padding: 0 0 0 1em;
display: block;
@include transition(background-color);
}
@include transition(padding-left, 0.2s);
}
.line-numbers {
cursor: default;
text-align: center;
overflow: hidden;
span {
padding: 0 0.5em;
display: block;
&:nth-child(5n) {
font-weight: bold;
}
}
max-width: 60px;
@include transition(max-width, 0.2s);
&.collapsed {
border: none;
max-width: 0;
}
}
}
div {
border-radius: inherit;
}
.line-number-control {
@include transition(left, 0.2s);
height: 26px;
width: 18px;
display: block;
position: absolute;
top: 0;
right: 0;
z-index: 1;
cursor: pointer;
// default: line numbers are shown
background: transparent url(icons/chevron-left.png);
&.show-line-numbers {
// override: line numbers are hidden
background: transparent url(icons/chevron-right.png);
}
}
}

View file

@ -0,0 +1,114 @@
.luminous { border-color: #444; }
.luminous .character{color:#FF69B4 !important;}
.luminous .comment{color:gray !important; font-style:italic !important;}
/* NOTE: TODO: etc. */
.luminous .comment_note{background-color:#E0FE37 !important;
color:black !important;
font-style:normal !important;}
.luminous .constant{color:#0073AD !important; font-weight:bold !important;}
.luminous .delimiter{ font-weight:bold !important;}
.luminous .esc{color:#478CA3 !important;}
.luminous .function{color:#99D1FF !important;}
.luminous .heredoc{color:#FF6767 !important;}
.luminous .htmltag{font-weight:bold !important; color:#66FFCC !important;}
.luminous .keyword{font-weight:bold !important; color:#00FF00 !important;}
.luminous .numeric {color:#B1B1FF !important;}
.luminous .operator{color:#68FF68 !important;}
.luminous .obj{color: #DCDCDC !important;}
.luminous .oo{color:#C0FFFF !important;}
.luminous .preprocessor{color:#DA1FDA !important;}
.luminous .shebang{font-weight:bold !important; font-style:normal !important; color:#C0FFC0 !important;}
.luminous .string{color:#FF6767 !important;}
.luminous .type{color:#5CA0E4 !important;}
/* not used*/
.luminous .user_function{color: #0083E1 !important; font-weight:bold !important;}
.luminous .value{color:green !important;}
.luminous .variable{color:#8989A9 !important;}
.luminous .doccomment{color:#008100 !important;}
.luminous .doctag {color:#CA60CA !important;}
.luminous .docstr {color:#FF6767 !important;}
.luminous .docproperty{color:#0095FF !important; font-weight:bold !important;}
.luminous .diff_header_new{color:green !important;}
.luminous .diff_header_old{color:red !important;}
.luminous .diff_range{color:#B1B1FF !important;}
.luminous .diff_old{color:#AF0000 !important;}
.luminous .diff_new{color:#00AF00 !important;}
.luminous .lang_latex{color:#B1B1FF !important;}
.luminous .latex_function{color:#FF6767 !important; font-weight:normal !important;}
.luminous .latex_operator{color:#676CFF !important;}
.luminous .make_target{color:magenta !important;}
.luminous .make_dep{color: #B1B1FF !important;}
.luminous .regex{color:#BE7CFF !important;}
.luminous .regex_subpattern{font-weight:bold !important; color:#FF6767 !important;}
.luminous .regex_class_marker{color:#0083E1 !important; font-weight:bold !important;}
.luminous .regex_subpattern_marker{color:green !important; font-weight:bold !important;}
.luminous .regex_repeat_marker {color:#B1B1FF !important;}
.luminous .regex_operator{color:green !important;}
/* Specific to the whitespace language */
.luminous .whitespace_space{background-color:#FF5151 !important;}
.luminous .whitespace_tab{background-color:#A1B6FF !important;}
.luminous .link, .luminous .link:visited
{
color:#00AAAA !important;
}
.luminous .link:hover
{
color:#00AA00 !important;
}
/* line numbers */
.luminous .line-numbers {
background-color:#1E1E1E !important;
border-right:1px solid #5F5F5F !important;
color:#BCFFBC !important;
}
.luminous
{
background-color:#1E1E1E !important;
color:#FFFDCC !important;
}
.luminous .code.numbered > span:nth-child(2n)
{
background-color: #232323;
}
.luminous .code .highlight
{
background-color:#404040;
}
.luminous .code.numbered .highlight:nth-child(2n)
{
background-color:#454545;
}
/* this stuff has to come after the other rules because it will probably
* override some of them
*/
.luminous .diff_old{
background-color:#7C4543 !important;
background-color: rgba(232, 87, 82, 0.35) !important;
color: white !important;
}
.luminous .diff_new{
background-color:#3F612C !important;
background-color: rgba(0, 191, 0, 0.35) !important;
color: white !important;
}
.luminous .diff_unchanged, .luminous .diff_unchanged * {
color: #888A85 !important;
background-color: transparent !important;
}

View file

@ -0,0 +1,163 @@
.luminous .character{color:#FF69B4 !important;}
.luminous .comment{color:gray !important; font-style:italic !important;}
/* NOTE: TODO: etc. */
.luminous .comment_note{
background-color:#FFB2B0 !important;
background-color:rgba(255, 178, 176, 0.70) !important;
color:black !important;
font-style:normal !important;}
.luminous .constant{color:#0073AD !important; font-weight:bold !important;}
.luminous .esc{color:#057CA3 !important;}
.luminous .function{color:purple !important;}
.luminous .heredoc{color:red !important;
background-color:#FFF8F8 !important;
background-color:rgba(255, 248, 248, 0.20) !important;
}
.luminous .htmltag{font-weight:bold !important; color:purple !important;}
.luminous .keyword{font-weight:bold !important; color:#0066FF !important;}
.luminous .numeric {color:blue !important;}
.luminous .operator{color:#0000BF !important;}
.luminous .oo{color:#339900 !important;}
.luminous .obj{color:#3366FF !important;}
.luminous .preprocessor{color:green !important;}
.luminous .shebang{font-weight:bold !important; font-style:normal !important; color:purple !important;}
.luminous .string{color:red !important;
background-color:#FFF8F8 !important;
background-color:rgba(255, 157, 157, 0.20) !important;
}
.luminous .type{color:#0057AE !important;}
/* not used*/
.luminous .user_function{color: #000099 !important; font-weight:bold !important;}
.luminous .value{color:green !important;}
.luminous .variable{color:#4545A9 !important;}
.luminous .doccomment{color:blue !important;}
.luminous .doctag {color:#CA60CA !important;}
.luminous .docstr {color:red !important;}
.luminous .docproperty{color:#0095FF !important; font-weight:bold !important;}
.luminous .lang_latex{color:blue !important;}
.luminous .latex_function{color:red !important; font-weight:normal !important;}
.luminous .latex_operator{color:black !important;}
.luminous .make_target{color:magenta !important;}
.luminous .make_dep{color: blue !important;}
.luminous .regex{color:#8000FF !important;
background-color:#F9F3FF !important;
background-color:rgba(231, 206, 255, 0.20) !important;
}
.luminous .regex_subpattern{font-weight:bold !important; color:black !important;}
.luminous .regex_class_marker{color:#0083E1 !important; font-weight:bold !important;}
.luminous .regex_subpattern_marker{color:green !important; font-weight:bold !important;}
.luminous .regex_repeat_marker {color:blue !important;}
.luminous .regex_operator{color:green !important;}
/* Specific to the whitespace language */
.luminous .whitespace_space{background-color:#FF5151 !important;}
.luminous .whitespace_tab{background-color:#A1B6FF !important;}
.luminous .diff_header_new{color:green !important;}
.luminous .diff_header_old{color:red !important;}
.luminous .diff_range{color:blue !important;}
.luminous .diff_old{background-color:#FFBFBF !important;
background-color: rgba(255, 191, 191, 0.5) !important;
}
.luminous .diff_new{background-color:#BFFFBF !important;
background-color: rgba(191, 255, 191, 0.5) !important;
}
.luminous .diff_unchanged{
color: #888888 !important;
}
.luminous .diff_unchanged * {
background-color: transparent !important;
color: #888888 !important;
}
.luminous .xml {
color: #666 !important;
}
.luminous .xml * {
color: black !important;
}
.luminous .xml .type {
color: #2C72C7 !important;
}
.luminous .xml .htmltag {
color: #00892C !important;
/* font-weight: normal !important; */
}
.luminous .xml .string {
color: #0057AE !important;
}
.luminous .xml .comment {
color: gray !important;
}
.luminous .interpolation * {
background-color: transparent !important;
}
.luminous .interpolation {
font-style: italic;
background-color: rgba(30, 80, 200, 0.1);
}
.luminous .delimiter {
font-weight:bold;
color: #644A9B;
}
.luminous .code
{
background-color: #FCFCFC !important;
}
.luminous a.link, .luminous a.link:visited
{
color:blue !important;
}
/* line numbers */
.luminous .line-numbers{
color:black !important;
background-color:#e0e0e0 !important;
border-right:1px solid black !important;
}
.luminous
{
color:black !important;
border-color: #999;
background-color: #fff;
}
.luminous .code.numbered > span:nth-child(2n)
{
background-color:#F3F3F3;
}
.luminous .code .highlight
{
background-color:#DFDEF6;
}
.luminous .code.numbered .highlight:nth-child(2n)
{
background-color:#DFDEF6;
}

View file

@ -0,0 +1,37 @@
/*
* Here we override some CSS to make a Luminous object more print-friendly.
* as well as some layout, we override a few colours of the light theme.
* This is because some colours do not print well (grey comments => green)
* and boldness appears to be dropped from keywords, thereby removing all their
* highlighting (these are changed to a purple colour)
*/
/* body {padding:0px !important; margin:0px !important;} */
div.luminous .metabar_buttons, div.luminous .metabar_fixed
{
display:none !important;
visibility:hidden !important;
}
div.luminous, .luminous .code, .luminous .line_numbers
{
height:auto !important;
}
.luminous pre.code, .luminous pre.line_numbers {
overflow:visible !important;
max-height:none !important;
}
div.luminous .comment{color:green !important;}
div.luminous .keyword {}
div.luminous table.code_container{height:auto !important;}
div.luminous div.code_container{max-height:none !important;
overflow:visible !important;
height:auto !important;
}
table.code_container {padding:0px !important;
border-collapse:collapse;
}
td {padding: 0px !important;}
td.lineno {border-right:1px solid black; }
td.code { padding-left: 1em !important;}

View file

@ -0,0 +1,130 @@
/*
* Based on the 'Birds of Paradise' theme
* http://joebergantine.com/werkstatt/birds-of-paradise
*/
.luminous { border-color: #666; }
.luminous .character{color:#DCA3A3;}
.luminous .comment{color:#8F6843; font-style:italic;}
.luminous .comment_note{color:#D24421;
font-style:normal; font-weight:bold;}
.luminous .constant{color:#EFAC32; font-weight:bold;}
.luminous .delimiter{ font-weight:bold;}
.luminous .esc{color:#FFFF00 ;}
.luminous .function{color:#EFAC32;}
.luminous .heredoc{color:#D9D762;}
.luminous .htmltag{font-weight:bold; color:#EFCB43;}
.luminous .keyword{font-weight:bold; color:#EF5D32; }
.luminous .numeric {color:#6C99BB;}
.luminous .operator{color:#F68895;}
.luminous .obj{color:#8E83BD;}
.luminous .oo{}
.luminous .preprocessor{color:#FF9D5B;}
.luminous .shebang{font-weight:bold; font-style:normal;
color:#FFFFFF;}
.luminous .string{color:#D9D762;}
.luminous .type{color:#EFAC32; }
.luminous .user_function{color: #FFE432; font-weight:bold;}
.luminous .value{color:#FF9074;}
.luminous .variable{color:#7DAF9C;}
/* italicised because otherwise it seems to blend into the rest of the
* source too much */
.luminous .doccomment{color:#D2D2D2; font-style:italic;}
.luminous .doctag {color:#C6D25A;}
.luminous .docstr {color:#FF9B9B;}
.luminous .docproperty{color:#D2B777; font-weight:bold;}
.luminous .diff_header_new{background-color:#2F33AB;}
.luminous .diff_header_old{background-color:#2F33AB;}
.luminous .diff_range{background-color:#2F33AB;}
/* .luminous .lang_latex{color:#b1b1ff;} */
.luminous .latex_function{color:#FFDCA8;}
.luminous .latex_operator{color:#888888;}
.luminous .code {}
.luminous .long_delim{font-weight:bold;}
.luminous .generics{color:#adff5a;}
.luminous .regex{color:#A19BE9;}
.luminous .regex_subpattern{font-weight:bold;
color:#B383ED;}
.luminous .regex_class_marker{color:#7CB7E1; font-weight:bold;}
.luminous .regex_subpattern_marker{color:#D684EE;
font-weight:bold;}
.luminous .regex_repeat_marker{color:#b1b1ff;}
.luminous .regex_operator{color:#8FC08F;}
.luminous .make_target{color:#FFD5FF;}
.luminous .make_dep{color: #b1b1ff;}
.luminous .whitespace_space{background-color:#C877A5;}
.luminous .whitespace_tab{background-color:#7D85D1;}
.luminous .link, .luminous .link:visited
{
color:#FFC0FF;
}
.luminous .link:hover
{
color:#FFC0FF;
}
/* line numbers */
.luminous .line-numbers {
background-color:#3E2C29;
border-right:1px solid #F5AEA3;
color: #CA7A2A;
}
.luminous
{
background-color:#322323;
color:#E6E1C4;
}
.luminous .code.numbered > span:nth-child(2n)
{
background-color: #3E2C29;
}
.luminous .code .highlight
{
background-color:#1F1611;
}
.luminous .code.numbered .highlight:nth-child(2n)
{
background-color: #1F1611;
}
/* this stuff has to come after the other rules because it will probably
* override some of them
*/
.luminous .diff_old{
background-color:#660000;
background-color: rgba(232, 87, 82, 0.35);
color: #E6E1DC;
}
.luminous .diff_new{
background-color:#144212;
background-color: rgba(0, 191, 0, 0.35);
color: #E6E1DC;
}
.luminous .diff_unchanged, .luminous .diff_unchanged * {
color: #888A85;
background-color: transparent;
}

View file

@ -0,0 +1,125 @@
.luminous .character{color:#FF69B4 !important;}
.luminous .comment{color:#CC9999 !important; font-style:italic !important;}
/* NOTE: TODO: etc. */
.luminous .comment_note{
background-color:#FFB2B0 !important;
background-color:rgba(255, 178, 176, 0.70) !important;
color:black !important;
font-style:normal !important;}
.luminous .constant{color:#0073AD !important; font-weight:bold !important;}
.luminous .delimiter{ font-weight:bold !important;}
.luminous .esc{color:#057CA3 !important;}
.luminous .function{color:#CC3399 !important;}
.luminous .htmltag{font-weight:bold !important; color:#006633 !important;}
.luminous .keyword{font-weight:bold !important; color:#6666CC !important;}
.luminous .numeric {color:#0099FF !important;}
.luminous .operator{color:green !important;}
.luminous .oo{color:#339900 !important;}
.luminous .obj{color:#3366FF !important;}
.luminous .preprocessor{color:#9999CC !important;}
.luminous .shebang{font-weight:bold !important; font-style:normal !important; color:purple !important;}
.luminous .string, .luminous .heredoc{color:#339966 !important;
/* background-color:#FFF8F8 !important;
background-color:rgba(255, 157, 157, 0.20) !important;*/
}
.luminous .type{color:#0057AE !important;}
/* not used*/
.luminous .user_function{color: #0083E1 !important; font-weight:bold !important;}
.luminous .value{color:green !important;}
.luminous .variable{color:#4545A9 !important;}
.luminous .doccomment{color:#9999CC !important;}
.luminous .doctag {color:#CA60CA !important;}
.luminous .docstr {color:#339966 !important;}
.luminous .docproperty{color:#0095FF !important; font-weight:bold !important;}
.luminous .diff_header_new{color:green !important;}
.luminous .diff_header_old{color:red !important;}
.luminous .diff_range{color:blue !important;}
.luminous .lang_latex{color:blue !important;}
.luminous .latex_function{color:red !important; font-weight:normal !important;}
.luminous .latex_operator{color:black !important;}
.luminous .make_target{color:#CC00FF !important;}
.luminous .make_dep{color: blue !important;}
.luminous .regex{color:#8000FF !important;
background-color:#F9F3FF !important;
background-color:rgba(231, 206, 255, 0.20) !important;
}
.luminous .regex_subpattern{font-weight:bold !important; color:black !important;}
.luminous .regex_class_marker{color:#0083E1 !important; font-weight:bold !important;}
.luminous .regex_subpattern_marker{color:green !important; font-weight:bold !important;}
.luminous .regex_repeat_marker {color:blue !important;}
.luminous .regex_operator{color:green !important;}
/* Specific to the whitespace language */
.luminous .whitespace_space{background-color:#FF5151 !important;}
.luminous .whitespace_tab{background-color:#A1B6FF !important;}
/* line numbers */
.luminous .line-numbers{
background-color:#F3EEFF !important;
border-right:1px solid #BED6E1 !important;
color: #000066 !important;
}
.luminous .code
{
background-color: #F3EEFF !important;
color: #000066 !important;
}
.luminous a.link, .luminous a.link:visited
{
color:blue !important;
}
.luminous
{
background-color: #fff;
color:black !important;
}
.luminous .code.numbered > span:nth-child(2n)
{
background-color:#FAF8FF;
}
.luminous .code .highlight
{
background-color:#BED6E1;
}
.luminous .code.numbered .highlight:nth-child(2n)
{
background-color:#BED6E1;
}
/* this stuff has to come after the other rules because it will probably
* override some of them
*/
.luminous .diff_old{
background-color:#E2AEC1 !important;
background-color: rgba(226, 174, 193, 0.65) !important;
color: #666 !important;
}
.luminous .diff_new{
background-color:#B7E2C6 !important;
background-color: rgba(156, 226, 181, 0.65) !important;
color: #666 !important;
}
.luminous .diff_unchanged, .luminous .diff_unchanged * {
color: #888A85 !important;
background-color: transparent !important;
}

View file

@ -0,0 +1,141 @@
/*
* Copyright 2010 Mark Watkinson
*
* This file is part of Luminous.
*
* Luminous 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 3 of the License, or
* (at your option) any later version.
*
* Luminous 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 Luminous. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
* Based on the Zenburn theme.
* Colours largely taken from:
https://spreadsheets.google.com/ccc?key=pKLGtBDTfcpZoRtJFr_eeAg&hl=en#gid=0
*/
.luminous { border-color: #555; }
.luminous .character{color:#DCA3A3 !important;}
.luminous .comment{color:#7F9F7F !important;}
.luminous .comment_note{color:#DFDFDF !important;
font-style:normal !important; font-weight:bold !important;}
.luminous .constant{color:#DCA3A3; font-weight:bold !important;}
.luminous .delimiter{ font-weight:bold !important;}
.luminous .esc{color:#478ca3 !important;}
.luminous .function{color:#efef8f !important;}
.luminous .heredoc{color:#cc9393 !important;}
.luminous .htmltag{font-weight:bold !important; color:#e89393 !important;}
.luminous .keyword{font-weight:bold !important; color:#f0dfaf !important; }
.luminous .numeric {color:#8cd0d3 !important;}
.luminous .operator{color:#888888 !important;}
.luminous .obj{color:#dfdfbf !important;}
.luminous .oo{}
.luminous .preprocessor{color:#ffcfaf !important;}
.luminous .shebang{font-weight:bold !important; font-style:normal !important;
color:#c0ffc0 !important;}
.luminous .string{color:#cc9393;}
.luminous .type{color:#dfdfbf !important; }
.luminous .user_function{color: #BAAD88 !important; font-weight:bold !important;}
.luminous .value{color:#C0FFFF !important;}
.luminous .variable{color:#f0dfaf !important;}
.luminous .doccomment{color:#82a282 !important;}
.luminous .doctag {color:#d7afaf !important;}
.luminous .docstr {color:#FF9B9B !important;}
.luminous .docproperty{color:#FFFF87 !important; font-weight:bold !important;}
.luminous .diff_header_new{color:#D7D7AF !important;}
.luminous .diff_header_old{color:#D7D7AF !important;}
.luminous .diff_range{color:#D7D7AF !important; }
.luminous .diff_old{color:#D7AFAF !important;}
.luminous .diff_new{color:#FFD7AF !important;}
/* .luminous .lang_latex{color:#b1b1ff;} */
.luminous .latex_function{color:#FFDCA8 !important;}
.luminous .latex_operator{color:#888888 !important;}
.luminous .code {}
.luminous .long_delim{font-weight:bold !important;}
.luminous .generics{color:#adff5a !important;}
.luminous .regex{color:#E6CDFF !important;}
.luminous .regex_subpattern{font-weight:bold !important;
color:#FF8D8D !important;}
.luminous .regex_class_marker{color:#7CB7E1; font-weight:bold;}
.luminous .regex_subpattern_marker{color:#8FC08F !important;
font-weight:bold !important;}
.luminous .regex_repeat_marker{color:#b1b1ff !important;}
.luminous .regex_operator{color:#8FC08F !important;}
.luminous .make_target{color:#FFD5FF !important;}
.luminous .make_dep{color: #b1b1ff !important;}
.luminous .whitespace_space{background-color:#ff5151 !important;}
.luminous .whitespace_tab{background-color:#a1b6ff !important;}
.luminous .link, .luminous .link:visited
{
color:#C0FFC0 !important;
}
.luminous .link:hover
{
color:#FFC0FF !important;
}
/* line numbers */
.luminous .line-numbers {
background-color:#444444 !important;
border-right:1px solid #5F5F5F !important;
color: #A8A8A8 !important;
}
.luminous {
background-color:#3a3a3a !important;
color:#DCDCCC !important;
}
.luminous .code.numbered > span:nth-child(2n) {
background-color: #404040;
}
.luminous .code .highlight
{
background-color:#6D6D6D;
}
.luminous .code.numbered .highlight:nth-child(2n) {
background-color: #6D6D6D ;
}
/* this stuff has to come after the other rules because it will probably
* override some of them
*/
.luminous .diff_old{
background-color:#7C4543 !important;
background-color: rgba(232, 87, 82, 0.15) !important;
}
.luminous .diff_new{
background-color:#3F612C !important;
background-color: rgba(128, 251, 120, 0.15) !important;
}
.luminous .diff_unchanged, .luminous .diff_unchanged * {
color: #888A85 !important;
background-color: transparent !important;
}

View file

@ -0,0 +1,47 @@
<?php
require_once('luminous.php');
?><!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: sans-serif;
}
table {
border-collapse: collapse;
}
td {
border: 1px solid gray;
padding: 0 1em;
}
th {
border-bottom: 1px solid black;
text-align:center;
font-weight:bold;
}
</style>
</head>
<body>
<table style='margin-left:auto; margin-right: auto;'>
<thead>
<tr>
<th style='border: 0px'></th>
<th>Language</th>
<th>Valid Codes</th>
</tr>
</thead>
<tbody>
<?php
$i = 0;
foreach(luminous::scanners() as $l=>$codes) { ?>
<tr>
<td><?php echo ++$i; ?></td>
<td><?php echo $l; ?></td>
<td><?php echo join(', ', $codes); ?></td>
</tr>
<?php } ?>
</tbody>
</table>
</body>
</html>

View file

@ -0,0 +1,58 @@
<?php
/*
* quick script checks whether or not we're running the current release by
* fetching the most recent release data from the site API and comparing it
* to the version we're running here
*/
include (dirname(__FILE__) . '/luminous.php');
$URL = 'http://luminous.asgaard.co.uk/index.php/ajax/luminous/version';
$DOWNLOAD_URL = 'http://luminous.asgaard.co.uk/index.php/download';
$cmd_line = PHP_SAPI === 'cli';
$version = LUMINOUS_VERSION;
function urlify($url) {
global $cmd_line;
return $cmd_line? $url : "<a href='$url'>$url</a>";
}
function _echo($string) {
echo wordwrap($string, 79) . "\n";
}
if (!$cmd_line) _echo('<pre>');
if ($version === 'master') {
_echo('You are using a development version, I cannot check how up to date it is.');
_echo('You can download the latest stable release from '
. urlify($DOWNLOAD_URL));
}
else {
_echo('Checking version...');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERAGENT, 'Luminous ' . LUMINOUS_VERSION
. ' version check');
$json = curl_exec($ch);
curl_close($ch);
if (!$json) {
_echo('Remote request failed. Try again later or visit '
. urlify($DOWNLOAD_URL) . ' to see what the latest version is');
} else {
$data = json_decode($json, true);
if ($data['release_number'] === $version || 'v' . $data['release_number'] === $version) {
_echo('You are up to date!');
} else {
$output = "You are not up to date: your version is " . $version
. " and the most recent release is " . $data['release_number']
. ", released " . $data['release_date'] . '. '
. ' Visit ' . urlify($DOWNLOAD_URL) . ' to upgrade';
_echo($output);
}
}
}
if (!$cmd_line) _echo('</pre>');