Yes – unfortunately. This is by design. In our defence, no correct PHP highligher exists. To see why, consider the following code fragment, which is completely valid PHP:
28 | >foo', 'php'); ?>
29 |
(On a related note, this code fragment actually breaks the editor this text was written in.)
30 |
Executing this code might produce valid HTML – or it might not, depending on the outcome of the coinflip. The editor (or, in our case, highlighter) has no idea about the outcome without executing the code. And even then, the result changes with every subsequent execution. But even if the above code were more predictable, it would have to be executed in order to determine how to highlight the code. This is not done by Hyperlight or any other highlighting engine. The output is therefore wrong in a few cases. Fortunately, such cases should be relatively rare.
52 | Does anybody know anything cool to put here?
53 |
54 |
Good syntax highlighting is crucial for many different kinds content providers. Therefore, syntax highlighting libraries for the web have always been a central part of web development. However, requirements have changed. With more sophistication in web design came the demand for libraries that create not only high-quality highlightings but also high-quality HTML and CSS code and that are easy to use and to extend.
55 |
Two libraries have raised the bar considerably: Pygments for Python, and CodeRay for Ruby. For PHP, on the other hand, there’s no modern library that fulfills all of these requirements. This is therefore an attempt to offer a remedy.
The user guide aims to ease the first steps in using Hyperlight. Luckily, the interface is really easy to use, mainly because it’s also really small (remember: this is a good thing). Most of the configuration goes on behind the scenes or in the CSS.
32 |
The first part will focus on the end user. However, most users will probably want to customize behaviour in one way or another. We will therefore also discuss how to modify or create themes and syntax definitions. And so, without further ado …
33 |
34 |
Getting Started
35 |
At this point, let’s assume that you have already downloaded and unzipped the package into its target location because let’s face it, who wants to have an umpteenth description of how to unzip an archive?
36 |
To use Hyperlight, all you have to do is to include the main file into your PHP source code and invoke the highlighting function. To highlight a source code, this is all you need to do:
37 |
38 |
To put this in some more context, imagine that you want to highlight the current file. Our program might not be self-replicating or self-modifying but it sure is self-embellishing.
39 |
40 |
It really can’t get much simpler than that.
41 |
Notice that we didn’t have to put special HTML tags around our code. hyperlight does this for us. But don’t worry about lack of control. This function has two more optional arguments that you can use to control how these surrounding tags should look like. The first controls which surrounding tag to use and defaults to – what a surprise – ', 'xml', 'code'); ?>. The second argument controls the attributes that the tag should have (in addition to the class). For a detailed description of how to use these arguments, read the reference entry on the hyperlight function.
42 |
43 |
44 |
Regardless of the fourth argument, the class attribute is always present and can’t be removed – and shouldn’t be: it’s necessary for the CSS themes to work.
45 |
46 |
47 |
PHP is something of a special case; it requires a <?php to start a PHP block. However, when posting code, this is often omitted because only a short snippet is posted. That’s fine. Hyperlight offers a special language tag for this rather unique case: iphp.
48 |
49 |
Another special case occurs when we want to highlight a file. Hyperlight provides a shortcut to do this: hyperlight_file. As an added bonus, if you pass a regular file to this function, you don’t need to specify the file’s language explicitly. Hyperlight tries to figure the right language out by itself, based on the filename extension.
50 |
Now, that’s all there really is to it. Told you it was easy. ;-) But trust me, it gets more interesting once we want to create our own themes or language definitions.
51 |
52 |
Creating Themes
53 |
The whole visual appearance of the highlighted code in Hyperlight is based on a few simple CSS rules. The strength of Hyperlight lies in the fact that these rules are controlled by class names that are the same across all language definitions, thereby making it easy to adapt one theme for all languages.
54 |
At the same time, a finer degree of control might be needed because one size doesn’t fit all. This is possible in three ways. First, language definitions can define mappings between different class names. Secondly, rules can be combined and nested. Lastly, if all else fails, code is also tagged with a language-specific class name. This can be used to establish a specific rule for one language only. Of course, these should be used sparingly because they make it much harder to develop colour themes that are usable across all language definitions. We will examine all these techniques in due course.
55 |
The Theme File
56 |
A theme is just a CSS stylesheet that defines a set of rules based on class names. Therefore, in order to write a theme you need to know the rudiments of CSS. To limit the scope of the styles and make the theme definitions interoperate nicely with other, existing styles, it’s recommended that you prefix all theme-specific rules with .
57 |
58 |
An Example
59 |
Let’s look at a small example theme file, actually a fragment of zenburn.css, which is used for this document.
60 |
61 |
Here, we can see three things:
62 |
63 |
64 |
The first rule sets up the environment. However, refrain from setting more specific information here. In particular, don’t set a border or a font face. These are settings that may be set elsewhere.
65 |
66 |
67 |
The third rule is nested: it applies only to “todo”s nested inside a comment. As an example, it might apply to
68 | TODO: i18n', 'xml'); ?>
69 |
70 |
71 |
The last rule, on the other hand, is a specialization. It applies only to built-in keywords and overrides the more general keyword styles. It applies to:
72 | isset', 'xml'); ?>
73 |
74 |
75 |
76 |
Core Theme Classes
77 |
Hyperlight uses mappings to unify the kinds of CSS classes used. This drastically reduces the number of possible class names across all languages, while still preserving a representative subset. All theme files should at least be aware of this subset. Notice that this doesn’t mean they should provide different styles for all possible rules – this would probably be a bad idea since it clutters the visual needlessly.
78 |
Here is an alphabetically sorted list of these core class names:
79 |
80 |
char – a character literal
81 |
comment – a source code comment
82 |
doc – a documentation tag; usually nested inside a comment
83 |
escaped – some escaped entity; usually nested inside a string
84 |
identifier – an identifier such as a variable or a function name
85 |
keyword – any keyword or reserved word in the language
86 |
87 |
builtin – a built-in function, such as
88 |
literal – a built-in literal, such as
89 |
operator – an operator keyword, such as
90 |
preprocessor – a preprocessor statement, such as in C++
91 |
type – a built-in data type, such as
92 |
93 |
94 |
number – a numeric literal
95 |
regex – a regular expression literal
96 |
string – a string literal
97 |
tag – a tag; this is mostly used in HTML but also elsewhere
98 |
todo – a “todo”-like annotation in a comment
99 |
100 |
101 |
Since languages such as HTML or CSS in particular use very different syntactical elements from other languages, it’s reasonable to reuse the above classes in other context. For example, HTML may redefine the keyword class for tag names.
102 |
51 |
52 |
53 |
55 |
--------------------------------------------------------------------------------
/hyperlight/examples/theme_switcher.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $('#switch-buttons a').each(function (i, btt) {
3 | $(btt).click(function () {
4 | if ($(this).hasClass('active'))
5 | return false;
6 | $('#switch-buttons a').each(function (i, btt) { $(btt).removeClass('active'); });
7 | var cssPath = '../colors/' + this.id.replace('theme-', '') + '.css';
8 | var cssAlreadyLoaded = false;
9 | $('pre.source-code').each(function (j, code) {
10 | $(code).fadeOut('normal', function() {
11 | if (!cssAlreadyLoaded) {
12 | cssAlreadyLoaded = true;
13 | $('link#theme').attr({href: cssPath});
14 | }
15 | });
16 | });
17 | $('pre.source-code').each(function (j, code) { $(code).fadeIn('normal'); });
18 | $(this).addClass('active');
19 | return false;
20 | });
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/hyperlight/graphics/body-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klmr/hyperlight/f32704a74c386fcd24b52dad91390b36398eaa86/hyperlight/graphics/body-background.png
--------------------------------------------------------------------------------
/hyperlight/graphics/head-backback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klmr/hyperlight/f32704a74c386fcd24b52dad91390b36398eaa86/hyperlight/graphics/head-backback.png
--------------------------------------------------------------------------------
/hyperlight/graphics/head-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/klmr/hyperlight/f32704a74c386fcd24b52dad91390b36398eaa86/hyperlight/graphics/head-background.png
--------------------------------------------------------------------------------
/hyperlight/hyperlight.php:
--------------------------------------------------------------------------------
1 | ">
34 | *
35 | * (Remove space between `?` and `>`).
36 | * Although this no longer occurs, it is fixed by checking for `$token === ''`
37 | * in the `emit*` methods. This should never happen anyway. Probably something
38 | * to do with the zero-width lookahead in the PHP syntax definition.
39 | *
40 | * - `hyperlight_calculate_fold_marks`: refactor, write proper handler
41 | *
42 | * - Line numbers (on client-side?)
43 | *
44 | */
45 |
46 | /**
47 | * Hyperlight source code highlighter for PHP.
48 | * @package hyperlight
49 | */
50 |
51 | /** @ignore */
52 | require_once('preg_helper.php');
53 |
54 | if (!function_exists('array_peek')) {
55 | /**
56 | * @internal
57 | * This does exactly what you think it does. */
58 | function array_peek(array &$array) {
59 | $cnt = count($array);
60 | return $cnt === 0 ? null : $array[$cnt - 1];
61 | }
62 | }
63 |
64 | /**
65 | * @internal
66 | * For internal debugging purposes.
67 | */
68 | function dump($obj, $descr = null) {
69 | if ($descr !== null)
70 | echo "
$descr
";
71 | ob_start();
72 | var_dump($obj);
73 | $dump = ob_get_clean();
74 | ?>errorSurrounding($code, $position)
88 | );
89 | }
90 |
91 | // Try to extract the location of the error more or less precisely.
92 | // Only used for a comprehensive display.
93 | private function errorSurrounding($code, $pos) {
94 | $size = 10;
95 | $begin = $pos < $size ? 0 : $pos - $size;
96 | $end = $pos + $size > strlen($code) ? strlen($code) : $pos + $size;
97 | $offs = $pos - $begin;
98 | return substr($code, $begin, $end - $begin) . "\n" . sprintf("%{$offs}s", '^');
99 | }
100 | }
101 |
102 | /**
103 | * Represents a nesting rule in the grammar of a language definition.
104 | *
105 | * Individual rules can either be represented by raw strings ("simple" rules) or
106 | * by a nesting rule. Nesting rules specify where they can start and end. Inside
107 | * a nesting rule, other rules may be applied (both simple and nesting).
108 | * For example, a nesting rule may define a string literal. Inside that string,
109 | * other rules may be applied that recognize escape sequences.
110 | *
111 | * To use a nesting rule, supply how it may start and end, e.g.:
112 | *
113 | * $string_rule = array('string' => new Rule('/"/', '/"/'));
114 | *
115 | * You also need to specify nested states:
116 | *
117 | * $string_states = array('string' => 'escaped');
118 | *
119 | * Now you can add another rule for escaped:
120 | *
121 | * $escaped_rule = array('escaped' => '/\\(x\d{1,4}|.)/');
122 | *
123 | */
124 | class Rule {
125 | /**
126 | * Common rules.
127 | */
128 |
129 | const ALL_WHITESPACE = '/(\s|\r|\n)+/';
130 | const C_IDENTIFIER = '/[a-z_][a-z0-9_]*/i';
131 | const C_COMMENT = '#//.*?\n|/\*.*?\*/#s';
132 | const C_MULTILINECOMMENT = '#/\*.*?\*/#s';
133 | const DOUBLEQUOTESTRING = '/"(?:\\\\"|.)*?"/s';
134 | const SINGLEQUOTESTRING = "/'(?:\\\\'|.)*?'/s";
135 | const C_DOUBLEQUOTESTRING = '/L?"(?:\\\\"|.)*?"/s';
136 | const C_SINGLEQUOTESTRING = "/L?'(?:\\\\'|.)*?'/s";
137 | const STRING = '/"(?:\\\\"|.)*?"|\'(?:\\\\\'|.)*?\'/s';
138 | const C_NUMBER = '/
139 | (?: # Integer followed by optional fractional part.
140 | (?:
141 | 0(?:
142 | x[0-9a-f]+
143 | |
144 | [0-7]*
145 | )
146 | |
147 | \d+
148 | )
149 | (?:\.\d*)?
150 | (?:e[+-]\d+)?
151 | )
152 | |
153 | (?: # Just the fractional part.
154 | (?:\.\d+)
155 | (?:e[+-]?\d+)?
156 | )
157 | /ix';
158 |
159 | private $_start;
160 | private $_end;
161 |
162 | /** @ignore */
163 | public function __construct($start, $end = null) {
164 | $this->_start = $start;
165 | $this->_end = $end;
166 | }
167 |
168 | /**
169 | * Returns the pattern with which this rule starts.
170 | * @return string
171 | */
172 | public function start() {
173 | return $this->_start;
174 | }
175 |
176 | /**
177 | * Returns the pattern with which this rule may end.
178 | * @return string
179 | */
180 | public function end() {
181 | return $this->_end;
182 | }
183 | }
184 |
185 | /**
186 | * Abstract base class of all Hyperlight language definitions.
187 | *
188 | * In order to define a new language definition, this class is inherited.
189 | * The only function that needs to be overridden is the constructor. Helper
190 | * functions from the base class can then be called to construct the grammar
191 | * and store additional information.
192 | * The name of the subclass must be of the schema {Lang}Language,
193 | * where {Lang} is a short, unique name for the language starting
194 | * with a capital letter and continuing in lower case. For example,
195 | * PhpLanguage is a valid name. The language definition must
196 | * reside in a file located at languages/{lang}.php. Here,
197 | * {lang} is the all-lowercase spelling of the name, e.g.
198 | * languages/php.php.
199 | *
200 | */
201 | abstract class HyperLanguage {
202 | private $_states = array();
203 | private $_rules = array();
204 | private $_mappings = array();
205 | private $_info = array();
206 | private $_extensions = array();
207 | private $_caseInsensitive = false;
208 | private $_postProcessors = array();
209 |
210 | private static $_languageCache = array();
211 | private static $_compiledLanguageCache = array();
212 | private static $_filetypes;
213 |
214 | /**
215 | * Indices for information.
216 | */
217 |
218 | const NAME = 1;
219 | const VERSION = 2;
220 | const AUTHOR = 10;
221 | const WEBSITE = 5;
222 | const EMAIL = 6;
223 |
224 | /**
225 | * Retrieves a language definition name based on a file extension.
226 | *
227 | * Uses the contents of the languages/filetypes file to
228 | * guess the language definition name from a file name extension.
229 | * This file has to be generated using the
230 | * collect-filetypes.php script every time the language
231 | * definitions have been changed.
232 | *
233 | * @param string $ext the file name extension.
234 | * @return string The language definition name or NULL.
235 | */
236 | public static function nameFromExt($ext) {
237 | if (self::$_filetypes === null) {
238 | $ft_content = file('languages/filetypes', 1);
239 |
240 | foreach ($ft_content as $line) {
241 | list ($name, $extensions) = explode(':', trim($line));
242 | $extensions = explode(',', $extensions);
243 | // Inverse lookup.
244 | foreach ($extensions as $extension)
245 | $ft_data[$extension] = $name;
246 | }
247 | self::$_filetypes = $ft_data;
248 | }
249 | $ext = strtolower($ext);
250 | return
251 | array_key_exists($ext, self::$_filetypes) ?
252 | self::$_filetypes[strtolower($ext)] : null;
253 | }
254 |
255 | public static function compile(HyperLanguage $lang) {
256 | $id = $lang->id();
257 | if (!isset(self::$_compiledLanguageCache[$id]))
258 | self::$_compiledLanguageCache[$id] = $lang->makeCompiledLanguage();
259 | return self::$_compiledLanguageCache[$id];
260 | }
261 |
262 | public static function compileFromName($lang) {
263 | return self::compile(self::fromName($lang));
264 | }
265 |
266 | protected static function exists($lang) {
267 | return isset(self::$_languageCache[$lang]) or
268 | file_exists("languages/$lang.php");
269 | }
270 |
271 | protected static function fromName($lang) {
272 | if (!isset(self::$_languageCache[$lang])) {
273 | require_once("languages/$lang.php");
274 | $klass = ucfirst("{$lang}Language");
275 | self::$_languageCache[$lang] = new $klass();
276 | }
277 | return self::$_languageCache[$lang];
278 | }
279 |
280 | public function id() {
281 | $klass = get_class($this);
282 | return strtolower(substr($klass, 0, strlen($klass) - strlen('Language')));
283 | }
284 |
285 | protected function setCaseInsensitive($value) {
286 | $this->_caseInsensitive = $value;
287 | }
288 |
289 | protected function addStates(array $states) {
290 | $this->_states = self::mergeProperties($this->_states, $states);
291 | }
292 |
293 | protected function getState($key) {
294 | return $this->_states[$key];
295 | }
296 |
297 | protected function removeState($key) {
298 | unset($this->_states[$key]);
299 | }
300 |
301 | protected function addRules(array $rules) {
302 | $this->_rules = self::mergeProperties($this->_rules, $rules);
303 | }
304 |
305 | protected function getRule($key) {
306 | return $this->_rules[$key];
307 | }
308 |
309 | protected function removeRule($key) {
310 | unset($this->_rules[$key]);
311 | }
312 |
313 | protected function addMappings(array $mappings) {
314 | // TODO Implement nested mappings.
315 | $this->_mappings = array_merge($this->_mappings, $mappings);
316 | }
317 |
318 | protected function getMapping($key) {
319 | return $this->_mappings[$key];
320 | }
321 |
322 | protected function removeMapping($key) {
323 | unset($this->_mappings[$key]);
324 | }
325 |
326 | protected function setInfo(array $info) {
327 | $this->_info = $info;
328 | }
329 |
330 | protected function setExtensions(array $extensions) {
331 | $this->_extensions = $extensions;
332 | }
333 |
334 | protected function addPostprocessing($rule, HyperLanguage $language) {
335 | $this->_postProcessors[$rule] = $language;
336 | }
337 |
338 | // protected function addNestedLanguage(HyperLanguage $language, $hoistBackRules) {
339 | // $prefix = get_class($language);
340 | // if (!is_array($hoistBackRules))
341 | // $hoistBackRules = array($hoistBackRules);
342 | //
343 | // $states = array(); // Step 1: states
344 | //
345 | // foreach ($language->_states as $stateName => $state) {
346 | // $prefixedRules = array();
347 | //
348 | // if (strstr($stateName, ' ')) {
349 | // $parts = explode(' ', $stateName);
350 | // $prefixed = array();
351 | // foreach ($parts as $part)
352 | // $prefixed[] = "$prefix$part";
353 | // $stateName = implode(' ', $prefixed);
354 | // }
355 | // else
356 | // $stateName = "$prefix$stateName";
357 | //
358 | // foreach ($state as $key => $rule) {
359 | // if (is_string($key) and is_array($rule)) {
360 | // $nestedRules = array();
361 | // foreach ($rule as $nestedRule)
362 | // $nestedRules[] = ($nestedRule === '') ? '' :
363 | // "$prefix$nestedRule";
364 | //
365 | // $prefixedRules["$prefix$key"] = $nestedRules;
366 | // }
367 | // else
368 | // $prefixedRules[] = "$prefix$rule";
369 | // }
370 | //
371 | // if ($stateName === 'init')
372 | // $prefixedRules = array_merge($hoistBackRules, $prefixedRules);
373 | //
374 | // $states[$stateName] = $prefixedRules;
375 | // }
376 | //
377 | // $rules = array(); // Step 2: rules
378 | // // Mappings need to set up already!
379 | // $mappings = array();
380 | //
381 | // foreach ($language->_rules as $ruleName => $rule) {
382 | // if (is_array($rule)) {
383 | // $nestedRules = array();
384 | // foreach ($rule as $nestedName => $nestedRule) {
385 | // if (is_string($nestedName)) {
386 | // $nestedRules["$prefix$nestedName"] = $nestedRule;
387 | // $mappings["$prefix$nestedName"] = $nestedName;
388 | // }
389 | // else
390 | // $nestedRules[] = $nestedRule;
391 | // }
392 | // $rules["$prefix$ruleName"] = $nestedRules;
393 | // }
394 | // else {
395 | // $rules["$prefix$ruleName"] = $rule;
396 | // $mappings["$prefix$ruleName"] = $ruleName;
397 | // }
398 | // }
399 | //
400 | // // Step 3: mappings.
401 | //
402 | // foreach ($language->_mappings as $ruleName => $cssClass) {
403 | // if (strstr($ruleName, ' ')) {
404 | // $parts = explode(' ', $ruleName);
405 | // $prefixed = array();
406 | // foreach ($parts as $part)
407 | // $prefixed[] = "$prefix$part";
408 | // $mappings[implode(' ', $prefixed)] = $cssClass;
409 | // }
410 | // else
411 | // $mappings["$prefix$ruleName"] = $cssClass;
412 | // }
413 | //
414 | // $this->addStates($states);
415 | // $this->addRules($rules);
416 | // $this->addMappings($mappings);
417 | //
418 | // return $prefix . 'init';
419 | // }
420 |
421 | private function makeCompiledLanguage() {
422 | return new HyperlightCompiledLanguage(
423 | $this->id(),
424 | $this->_info,
425 | $this->_extensions,
426 | $this->_states,
427 | $this->_rules,
428 | $this->_mappings,
429 | $this->_caseInsensitive,
430 | $this->_postProcessors
431 | );
432 | }
433 |
434 | private static function mergeProperties(array $old, array $new) {
435 | foreach ($new as $key => $value) {
436 | if (is_string($key)) {
437 | if (isset($old[$key]) and is_array($old[$key]))
438 | $old[$key] = array_merge($old[$key], $new);
439 | else
440 | $old[$key] = $value;
441 | }
442 | else
443 | $old[] = $value;
444 | }
445 |
446 | return $old;
447 | }
448 | }
449 |
450 | class HyperlightCompiledLanguage {
451 | private $_id;
452 | private $_info;
453 | private $_extensions;
454 | private $_states;
455 | private $_rules;
456 | private $_mappings;
457 | private $_caseInsensitive;
458 | private $_postProcessors = array();
459 |
460 | public function __construct($id, $info, $extensions, $states, $rules, $mappings, $caseInsensitive, $postProcessors) {
461 | $this->_id = $id;
462 | $this->_info = $info;
463 | $this->_extensions = $extensions;
464 | $this->_caseInsensitive = $caseInsensitive;
465 | $this->_states = $this->compileStates($states);
466 | $this->_rules = $this->compileRules($rules);
467 | $this->_mappings = $mappings;
468 |
469 | foreach ($postProcessors as $ppkey => $ppvalue)
470 | $this->_postProcessors[$ppkey] = HyperLanguage::compile($ppvalue);
471 | }
472 |
473 | public function id() {
474 | return $this->_id;
475 | }
476 |
477 | public function name() {
478 | return $this->_info[HyperLanguage::NAME];
479 | }
480 |
481 | public function authorName() {
482 | if (!array_key_exists(HyperLanguage::AUTHOR, $this->_info))
483 | return null;
484 | $author = $this->_info[HyperLanguage::AUTHOR];
485 | if (is_string($author))
486 | return $author;
487 | if (!array_key_exists(HyperLanguage::NAME, $author))
488 | return null;
489 | return $author[HyperLanguage::NAME];
490 | }
491 |
492 | public function authorWebsite() {
493 | if (!array_key_exists(HyperLanguage::AUTHOR, $this->_info) or
494 | !is_array($this->_info[HyperLanguage::AUTHOR]) or
495 | !array_key_exists(HyperLanguage::WEBSITE, $this->_info[HyperLanguage::AUTHOR]))
496 | return null;
497 | return $this->_info[HyperLanguage::AUTHOR][HyperLanguage::WEBSITE];
498 | }
499 |
500 | public function authorEmail() {
501 | if (!array_key_exists(HyperLanguage::AUTHOR, $this->_info) or
502 | !is_array($this->_info[HyperLanguage::AUTHOR]) or
503 | !array_key_exists(HyperLanguage::EMAIL, $this->_info[HyperLanguage::AUTHOR]))
504 | return null;
505 | return $this->_info[HyperLanguage::AUTHOR][HyperLanguage::EMAIL];
506 | }
507 |
508 | public function authorContact() {
509 | $email = $this->authorEmail();
510 | return $email !== null ? $email : $this->authorWebsite();
511 | }
512 |
513 | public function extensions() {
514 | return $this->_extensions;
515 | }
516 |
517 | public function state($stateName) {
518 | return $this->_states[$stateName];
519 | }
520 |
521 | public function rule($ruleName) {
522 | return $this->_rules[$ruleName];
523 | }
524 |
525 | public function className($state) {
526 | if (array_key_exists($state, $this->_mappings))
527 | return $this->_mappings[$state];
528 | else if (strstr($state, ' ') === false)
529 | // No mapping for state.
530 | return $state;
531 | else {
532 | // Try mapping parts of nested state name.
533 | $parts = explode(' ', $state);
534 | $ret = array();
535 |
536 | foreach ($parts as $part) {
537 | if (array_key_exists($part, $this->_mappings))
538 | $ret[] = $this->_mappings[$part];
539 | else
540 | $ret[] = $part;
541 | }
542 |
543 | return implode(' ', $ret);
544 | }
545 | }
546 |
547 | public function postProcessors() {
548 | return $this->_postProcessors;
549 | }
550 |
551 | private function compileStates($states) {
552 | $ret = array();
553 |
554 | foreach ($states as $name => $state) {
555 | $newstate = array();
556 |
557 | if (!is_array($state))
558 | $state = array($state);
559 |
560 | foreach ($state as $key => $elem) {
561 | if ($elem === null)
562 | continue;
563 | if (is_string($key)) {
564 | if (!is_array($elem))
565 | $elem = array($elem);
566 |
567 | foreach ($elem as $el2) {
568 | if ($el2 === '')
569 | $newstate[] = $key;
570 | else
571 | $newstate[] = "$key $el2";
572 | }
573 | }
574 | else
575 | $newstate[] = $elem;
576 | }
577 |
578 | $ret[$name] = $newstate;
579 | }
580 |
581 | return $ret;
582 | }
583 |
584 | private function compileRules($rules) {
585 | $tmp = array();
586 |
587 | // Preprocess keyword list and flatten nested lists:
588 |
589 | // End of regular expression matching keywords.
590 | $end = $this->_caseInsensitive ? ')\b/i' : ')\b/';
591 |
592 | foreach ($rules as $name => $rule) {
593 | if (is_array($rule)) {
594 | if (self::isAssocArray($rule)) {
595 | // Array is a nested list of rules.
596 | foreach ($rule as $key => $value) {
597 | if (is_array($value))
598 | // Array represents a list of keywords.
599 | $value = '/\b(?:' . implode('|', $value) . $end;
600 |
601 | if (!is_string($key) or strlen($key) === 0)
602 | $tmp[$name] = $value;
603 | else
604 | $tmp["$name $key"] = $value;
605 | }
606 | }
607 | else {
608 | // Array represents a list of keywords.
609 | $rule = '/\b(?:' . implode('|', $rule) . $end;
610 | $tmp[$name] = $rule;
611 | }
612 | }
613 | else {
614 | $tmp[$name] = $rule;
615 | } // if (is_array($rule))
616 | } // foreach
617 |
618 | $ret = array();
619 |
620 | foreach ($this->_states as $name => $state) {
621 | $regex_rules = array();
622 | $regex_names = array();
623 | $nesting_rules = array();
624 |
625 | foreach ($state as $rule_name) {
626 | $rule = $tmp[$rule_name];
627 | if ($rule instanceof Rule)
628 | $nesting_rules[$rule_name] = $rule;
629 | else {
630 | $regex_rules[] = $rule;
631 | $regex_names[] = $rule_name;
632 | }
633 | }
634 |
635 | $ret[$name] = array_merge(
636 | array(preg_merge('|', $regex_rules, $regex_names)),
637 | $nesting_rules
638 | );
639 | }
640 |
641 | return $ret;
642 | }
643 |
644 | private static function isAssocArray(array $array) {
645 | foreach($array as $key => $_)
646 | if (is_string($key))
647 | return true;
648 | return false;
649 | }
650 | }
651 |
652 | class Hyperlight {
653 | private $_lang;
654 | private $_result;
655 | private $_states;
656 | private $_omitSpans;
657 | private $_postProcessors = array();
658 |
659 | public function __construct($lang) {
660 | if (is_string($lang))
661 | $this->_lang = HyperLanguage::compileFromName(strtolower($lang));
662 | else if ($lang instanceof HyperlightCompiledLanguage)
663 | $this->_lang = $lang;
664 | else if ($lang instanceof HyperLanguage)
665 | $this->_lang = HyperLanguage::compile($lang);
666 | else
667 | trigger_error(
668 | 'Invalid argument type for $lang to Hyperlight::__construct',
669 | E_USER_ERROR
670 | );
671 |
672 | foreach ($this->_lang->postProcessors() as $ppkey => $ppvalue)
673 | $this->_postProcessors[$ppkey] = new Hyperlight($ppvalue);
674 |
675 | $this->reset();
676 | }
677 |
678 | public function language() {
679 | return $this->_lang;
680 | }
681 |
682 | public function reset() {
683 | $this->_states = array('init');
684 | $this->_omitSpans = array();
685 | }
686 |
687 | public function render($code) {
688 | // Normalize line breaks.
689 | $this->_code = preg_replace('/\r\n?/', "\n", $code);
690 | $fm = hyperlight_calculate_fold_marks($this->_code, $this->language()->id());
691 | return hyperlight_apply_fold_marks($this->renderCode(), $fm);
692 | }
693 |
694 | public function renderAndPrint($code) {
695 | echo $this->render($code);
696 | }
697 |
698 |
699 | private function renderCode() {
700 | $code = $this->_code;
701 | $pos = 0;
702 | $len = strlen($code);
703 | $this->_result = '';
704 | $state = array_peek($this->_states);
705 |
706 | // If there are open states (reentrant parsing), open the corresponding
707 | // tags first:
708 |
709 | for ($i = 1; $i < count($this->_states); ++$i)
710 | if (!$this->_omitSpans[$i - 1]) {
711 | $class = $this->_lang->className($this->_states[$i]);
712 | $this->write("");
713 | }
714 |
715 | // Emergency break to catch faulty rules.
716 | $prev_pos = -1;
717 |
718 | while ($pos < $len) {
719 | // The token next to the current position, after the inner loop completes.
720 | // i.e. $closest_hit = array($matched_text, $position)
721 | $closest_hit = array('', $len);
722 | // The rule that found this token.
723 | $closest_rule = null;
724 | $rules = $this->_lang->rule($state);
725 |
726 | foreach ($rules as $name => $rule) {
727 | if ($rule instanceof Rule)
728 | $this->matchIfCloser(
729 | $rule->start(), $name, $pos, $closest_hit, $closest_rule
730 | );
731 | else if (preg_match($rule, $code, $matches, PREG_OFFSET_CAPTURE, $pos) == 1) {
732 | // Search which of the sub-patterns matched.
733 |
734 | foreach ($matches as $group => $match) {
735 | if (!is_string($group))
736 | continue;
737 | if ($match[1] !== -1) {
738 | $closest_hit = $match;
739 | $closest_rule = str_replace('_', ' ', $group);
740 | break;
741 | }
742 | }
743 | }
744 | } // foreach ($rules)
745 |
746 | // If we're currently inside a rule, check whether we've come to the
747 | // end of it, or the end of any other rule we're nested in.
748 |
749 | if (count($this->_states) > 1) {
750 | $n = count($this->_states) - 1;
751 | do {
752 | $rule = $this->_lang->rule($this->_states[$n - 1]);
753 | $rule = $rule[$this->_states[$n]];
754 | --$n;
755 | if ($n < 0)
756 | throw new NoMatchingRuleException($this->_states, $pos, $code);
757 | } while ($rule->end() === null);
758 |
759 | $this->matchIfCloser($rule->end(), $n + 1, $pos, $closest_hit, $closest_rule);
760 | }
761 |
762 | // We take the closest hit:
763 |
764 | if ($closest_hit[1] > $pos)
765 | $this->emit(substr($code, $pos, $closest_hit[1] - $pos));
766 |
767 | $prev_pos = $pos;
768 | $pos = $closest_hit[1] + strlen($closest_hit[0]);
769 |
770 | if ($prev_pos === $pos and is_string($closest_rule))
771 | if (array_key_exists($closest_rule, $this->_lang->rule($state))) {
772 | array_push($this->_states, $closest_rule);
773 | $state = $closest_rule;
774 | $this->emitPartial('', $closest_rule);
775 | }
776 |
777 | if ($closest_hit[1] === $len)
778 | break;
779 | else if (!is_string($closest_rule)) {
780 | // Pop state.
781 | if (count($this->_states) <= $closest_rule)
782 | throw new NoMatchingRuleException($this->_states, $pos, $code);
783 |
784 | while (count($this->_states) > $closest_rule + 1) {
785 | $lastState = array_pop($this->_states);
786 | $this->emitPop('', $lastState);
787 | }
788 | $lastState = array_pop($this->_states);
789 | $state = array_peek($this->_states);
790 | $this->emitPop($closest_hit[0], $lastState);
791 | }
792 | else if (array_key_exists($closest_rule, $this->_lang->rule($state))) {
793 | // Push state.
794 | array_push($this->_states, $closest_rule);
795 | $state = $closest_rule;
796 | $this->emitPartial($closest_hit[0], $closest_rule);
797 | }
798 | else
799 | $this->emit($closest_hit[0], $closest_rule);
800 | } // while ($pos < $len)
801 |
802 | // Close any tags that are still open (can happen in incomplete code
803 | // fragments that don't necessarily signify an error (consider PHP
804 | // embedded in HTML, or a C++ preprocessor code not ending on newline).
805 |
806 | $omitSpansBackup = $this->_omitSpans;
807 | for ($i = count($this->_states); $i > 1; --$i)
808 | $this->emitPop();
809 | $this->_omitSpans = $omitSpansBackup;
810 |
811 | return $this->_result;
812 | }
813 |
814 | private function matchIfCloser($expr, $next, $pos, &$closest_hit, &$closest_rule) {
815 | $matches = array();
816 | if (preg_match($expr, $this->_code, $matches, PREG_OFFSET_CAPTURE, $pos) == 1) {
817 | if (
818 | (
819 | // Two hits at same position -- compare length
820 | // For equal lengths: first come, first serve.
821 | $matches[0][1] == $closest_hit[1] and
822 | strlen($matches[0][0]) > strlen($closest_hit[0])
823 | ) or
824 | $matches[0][1] < $closest_hit[1]
825 | ) {
826 | $closest_hit = $matches[0];
827 | $closest_rule = $next;
828 | }
829 | }
830 | }
831 |
832 | private function processToken($token) {
833 | if ($token === '')
834 | return '';
835 | $nest_lang = array_peek($this->_states);
836 | if (array_key_exists($nest_lang, $this->_postProcessors))
837 | return $this->_postProcessors[$nest_lang]->render($token);
838 | else
839 | #return self::htmlentities($token);
840 | return htmlspecialchars($token, ENT_NOQUOTES);
841 | }
842 |
843 | private function emit($token, $class = '') {
844 | $token = $this->processToken($token);
845 | if ($token === '')
846 | return;
847 | $class = $this->_lang->className($class);
848 | if ($class === '')
849 | $this->write($token);
850 | else
851 | $this->write("$token");
852 | }
853 |
854 | private function emitPartial($token, $class) {
855 | $token = $this->processToken($token);
856 | $class = $this->_lang->className($class);
857 | if ($class === '') {
858 | if ($token !== '')
859 | $this->write($token);
860 | array_push($this->_omitSpans, true);
861 | }
862 | else {
863 | $this->write("$token");
864 | array_push($this->_omitSpans, false);
865 | }
866 | }
867 |
868 | private function emitPop($token = '', $class = '') {
869 | $token = $this->processToken($token);
870 | if (array_pop($this->_omitSpans))
871 | $this->write($token);
872 | else
873 | $this->write("$token");
874 | }
875 |
876 | private function write($text) {
877 | $this->_result .= $text;
878 | }
879 |
880 | // // DAMN! What did I need them for? Something to do with encoding …
881 | // // but why not use the `$charset` argument on `htmlspecialchars`?
882 | // private static function htmlentitiesCallback($match) {
883 | // switch ($match[0]) {
884 | // case '<': return '<';
885 | // case '>': return '>';
886 | // case '&': return '&';
887 | // }
888 | // }
889 | //
890 | // private static function htmlentities($text) {
891 | // return htmlspecialchars($text, ENT_NOQUOTES);
892 | // return preg_replace_callback(
893 | // '/[<>&]/', array('Hyperlight', 'htmlentitiesCallback'), $text
894 | // );
895 | // }
896 | } // class Hyperlight
897 |
898 | /**
899 | * echos a highlighted code.
900 | *
901 | * For example, the following
902 | *
903 | * hyperlight('', 'php');
904 | *
905 | * results in:
906 | *
907 | *
...
908 | *
909 | *
910 | * @param string $code The code.
911 | * @param string $lang The language of the code.
912 | * @param string $tag The surrounding tag to use. Optional.
913 | * @param array $attributes Attributes to decorate {@link $tag} with.
914 | * If no tag is given, this argument can be passed in its place. This
915 | * behaviour will be assumed if the third argument is an array.
916 | * Attributes must be given as a hash of key value pairs.
917 | */
918 | function hyperlight($code, $lang, $tag = 'pre', array $attributes = array()) {
919 | if ($code == '')
920 | die("`hyperlight` needs a code to work on!");
921 | if ($lang == '')
922 | die("`hyperlight` needs to know the code's language!");
923 | if (is_array($tag) and !empty($attributes))
924 | die("Can't pass array arguments for \$tag *and* \$attributes to `hyperlight`!");
925 | if ($tag == '')
926 | $tag = 'pre';
927 | if (is_array($tag)) {
928 | $attributes = $tag;
929 | $tag = 'pre';
930 | }
931 | $lang = htmlspecialchars(strtolower($lang));
932 | $class = "source-code $lang";
933 |
934 | $attr = array();
935 | foreach ($attributes as $key => $value) {
936 | if ($key == 'class')
937 | $class .= ' ' . htmlspecialchars($value);
938 | else
939 | $attr[] = htmlspecialchars($key) . '="' .
940 | htmlspecialchars($value) . '"';
941 | }
942 |
943 | $attr = empty($attr) ? '' : ' ' . implode(' ', $attr);
944 |
945 | $hl = new Hyperlight($lang);
946 | echo "<$tag class=\"$class\"$attr>";
947 | $hl->renderAndPrint(trim($code));
948 | echo "$tag>";
949 | }
950 |
951 | /**
952 | * Is the same as:
953 | *
954 | * hyperlight(file_get_contents($filename), $lang, $tag, $attributes);
955 | *
956 | * @see hyperlight()
957 | */
958 | function hyperlight_file($filename, $lang = null, $tag = 'pre', array $attributes = array()) {
959 | if ($lang == '') {
960 | // Try to guess it from file extension.
961 | $pos = strrpos($filename, '.');
962 | if ($pos !== false) {
963 | $ext = substr($filename, $pos + 1);
964 | $lang = HyperLanguage::nameFromExt($ext);
965 | }
966 | }
967 | hyperlight(file_get_contents($filename), $lang, $tag, $attributes);
968 | }
969 |
970 | if (defined('HYPERLIGHT_SHORTCUT')) {
971 | function hy() {
972 | $args = func_get_args();
973 | call_user_func_array('hyperlight', $args);
974 | }
975 | function hyf() {
976 | $args = func_get_args();
977 | call_user_func_array('hyperlight_file', $args);
978 | }
979 | }
980 |
981 | function hyperlight_calculate_fold_marks($code, $lang) {
982 | $supporting_languages = array('csharp', 'vb');
983 |
984 | if (!in_array($lang, $supporting_languages))
985 | return array();
986 |
987 | $fold_begin_marks = array('/^\s*#Region/', '/^\s*#region/');
988 | $fold_end_marks = array('/^\s*#End Region/', '/\s*#endregion/');
989 |
990 | $lines = preg_split('/\r|\n|\r\n/', $code);
991 |
992 | $fold_begin = array();
993 | foreach ($fold_begin_marks as $fbm)
994 | $fold_begin = $fold_begin + preg_grep($fbm, $lines);
995 |
996 | $fold_end = array();
997 | foreach ($fold_end_marks as $fem)
998 | $fold_end = $fold_end + preg_grep($fem, $lines);
999 |
1000 | if (count($fold_begin) !== count($fold_end) or count($fold_begin) === 0)
1001 | return array();
1002 |
1003 | $fb = array();
1004 | $fe = array();
1005 | foreach ($fold_begin as $line => $_)
1006 | $fb[] = $line;
1007 |
1008 | foreach ($fold_end as $line => $_)
1009 | $fe[] = $line;
1010 |
1011 | $ret = array();
1012 | for ($i = 0; $i < count($fb); $i++)
1013 | $ret[$fb[$i]] = $fe[$i];
1014 |
1015 | return $ret;
1016 | }
1017 |
1018 | function hyperlight_apply_fold_marks($code, array $fold_marks) {
1019 | if ($fold_marks === null or count($fold_marks) === 0)
1020 | return $code;
1021 |
1022 | $lines = explode("\n", $code);
1023 |
1024 | foreach ($fold_marks as $begin => $end) {
1025 | $lines[$begin] = '' . $lines[$begin] . '';
1026 | $lines[$begin + 1] = '' . $lines[$begin + 1];
1027 | $lines[$end + 1] = '' . $lines[$end + 1];
1028 | }
1029 |
1030 | return implode("\n", $lines);
1031 | }
1032 |
1033 | ?>
1034 |
--------------------------------------------------------------------------------
/hyperlight/index.php:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 | ‹? Hyperlight ?› Code Highlighting for PHP
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Code Highlighting for PHP
18 |
19 |
20 |
21 |
22 |
23 |
Why use Hyperlight?
24 |
25 |
26 |
Easy to use. There’s no configuration. The following code will highlight your source code. Nothing more needs to be said or done.
27 | theResult();', 'iphp'); ?>
30 |
Even easier, there’s a handy function hyperlight for lightweight usage, especially in HTML templates:
31 | ', 'php'); ?>
32 |
This code creates a <pre> container around the code. This can be controlled with a third argument to the function.
33 |
34 |
35 |
Easy to extend. The syntax definitions are written in PHP but only very basic language grasp is needed. Syntax definitions are concise and for most tasks, existing templates can be used and it’s enough to customize a basic set of features.
36 |
37 |
38 |
Powerful. The syntax definitions use regular expressions but they support stateful parsing through a very simple mechanism. This makes implementing context free grammars effortless.
39 |
40 |
41 |
Full CSS support. One single CSS file can be used for all languages to give a consistent look & feel. Elements may be nested for refinements (e.g. highlighting “TODO” items in comments):
42 |
44 |
Further refinements are possible in order to differentiate similar elements. Consider the different classes of keywords:
45 |
48 |
49 |
50 |
Colour schemes! – This is basically the same as “full CSS support” but it sounds waaay cooler. Since CSS support is naturally included in Hyperlight and syntax files can define appropriate mappings for their lexemes, usage and creation of professional colour schemes is effortless.
51 |
52 |
53 |
54 |
Why not use something else?
55 |
Sure, there are alternatives. Unfortunately, they are surprisingly few for PHP:
56 |
57 |
Geshi
58 |
If you’re forced to work with PHP version < 5.0, sure, use Geshi. But be prepared that each syntax brings its own (ugly) style, lacking conventions make the use of one CSS for all languages impossible (because they use the same CSS class names for completely different things), a lot of badly-documented configuration is necessary to get the desired result, HTML garbage is produced and the CSS class names are gibberish.
59 |
Furthermore, many of the syntax definitions are badly realized and/or have bugs. Creating an own highlighting isn't trivial because the API is quite complicated, not very powerful and lacks documentation.
60 |
If that doesn't matter to you, Geshi is perhaps not such a bad choice.
61 |
62 |
Pear_TextHighlighter
63 |
Syntax definitions must be given as cumbersome XML files. Need I say more?
";
97 | return $index;
98 | }
99 |
100 | function hyperlight_insert_block($match) {
101 | global $hyperlight_codes;
102 | return $hyperlight_codes[$match[0]];
103 | }
104 |
105 | ?>
106 |
--------------------------------------------------------------------------------
/hyperlight/plugins/wordpress/readme.txt:
--------------------------------------------------------------------------------
1 | === Plugin Name ===
2 | Contributors: Konrad Rudolph
3 | Tags: syntax highlighting, syntax highlight, syntax formatting, code formatting, code, formatting, highlight, syntax
4 | Requires at least: 2.0.2
5 | Tested up to: 2.8.6
6 | Stable tag: trunk
7 |
8 | A code highlighting plugin for WordPress that just works, and is highly configurable using CSS.
9 |
10 | == Description ==
11 |
12 | Hyperlight highlights source code, pure and simple. It's
13 |
14 | * **Easy to use** -- using it is a matter of one function call.
15 | * **Easy to extend** -- write your own language definitions in PHP using regular expressions.
16 | * **Powerful** -- since the parser supports states, it can do so much more than just regular languages.
17 | * **Compliant** -- Hyperlight produces valid, semantic strict XHTML.
18 | * **Configurable** -- Hyperlight produces logical CSS rules which can be used by beautiful colour themes.
19 |
20 | == Installation ==
21 |
22 | 1. Create a folder called `hyperlight` in the `/wp-content/plugins/` directory.
23 | 1. Copy the file `hyperlight.php` from the current folder into this folder.
24 | 1. Create (yet another!) a sub-folder called `hyperlight`.
25 | 1. Copy all the hyperlight files there.
26 |
27 | You should end up with a directory structure like this:
28 |
29 | * `hyperlight/`
30 | * `hyperlight.php` -- The WordPress plugin file
31 | * `readme.txt` -- This file
32 | * `hyperlight/`
33 | * ... all Hyperlight files, in particular:
34 | * `hyperlight.php` -- The main Hyperlight include file
35 |
36 | == Frequently Asked Questions ==
37 |
38 | = How do I highlight code? =
39 |
40 | Code (in `
` tags) is highlighted automatically if its `
` tag has an attribute `lang`. For example:
41 |
42 |
43 |
44 |
45 |
46 | = Code doesn't appear formatted! =
47 |
48 | Hyperlight only parses the code and adds appropriate CSS class tags to the HTML output. In order for the code to appear coloured you need to use a Hyperlight colour scheme which consists of a single CSS file that you can drop into your WordPress theme.
49 | Refer to the [Hyperlight documentation](http://code.google.com/p/hyperlight/wiki/UserGuide) for more detail.
50 |
51 | == Changelog ==
52 |
53 | = 0.1 =
54 | * Initial WordPress version.
55 |
--------------------------------------------------------------------------------
/hyperlight/preg_helper.php:
--------------------------------------------------------------------------------
1 | different modifiers on the individual expressions. The order of
36 | * sub-matches is preserved as well. Numbered back-references are adapted to
37 | * the new overall sub-match count. This means that it's safe to use numbered
38 | * back-refences in the individual expressions!
39 | * If {@link $names} is given, the individual expressions are captured in
40 | * named sub-matches using the contents of that array as names.
41 | * Matching pair-delimiters (e.g. "{…}") are currently
42 | * not supported.
43 | *
44 | * The function assumes that all regular expressions are well-formed.
45 | * Behaviour is undefined if they aren't.
46 | *
47 | * This function was created after a
48 | * {@link http://stackoverflow.com/questions/244959/ StackOverflow discussion}.
49 | * Much of it was written or thought of by “porneL” and “eyelidlessness”. Many
50 | * thanks to both of them.
51 | *
52 | * @param string $glue A string to insert between the individual expressions.
53 | * This should usually be either the empty string, indicating
54 | * concatenation, or the pipe ("|"), indicating alternation.
55 | * Notice that this string might have to be escaped since it is treated
56 | * as a normal character in a regular expression (i.e. "/" will
57 | * end the expression and result in an invalid output).
58 | * @param array $expressions The expressions to merge. The expressions may
59 | * have arbitrary different delimiters and modifiers.
60 | * @param array $names Optional. This is either an empty array or an array of
61 | * strings of the same length as {@link $expressions}. In that case,
62 | * the strings of this array are used to create named sub-matches for the
63 | * expressions.
64 | * @return string An string representing a regular expression equivalent to the
65 | * merged expressions. Returns FALSE if an error occurred.
66 | */
67 | function preg_merge($glue, array $expressions, array $names = array()) {
68 | // … then, a miracle occurs.
69 |
70 | // Sanity check …
71 |
72 | $use_names = ($names !== null and count($names) !== 0);
73 |
74 | if (
75 | $use_names and count($names) !== count($expressions) or
76 | !is_string($glue)
77 | )
78 | return false;
79 |
80 | $result = array();
81 | // For keeping track of the names for sub-matches.
82 | $names_count = 0;
83 | // For keeping track of *all* captures to re-adjust backreferences.
84 | $capture_count = 0;
85 |
86 | foreach ($expressions as $expression) {
87 | if ($use_names)
88 | $name = str_replace(' ', '_', $names[$names_count++]);
89 |
90 | // Get delimiters and modifiers:
91 |
92 | $stripped = preg_strip($expression);
93 |
94 | if ($stripped === false)
95 | return false;
96 |
97 | list($sub_expr, $modifiers) = $stripped;
98 |
99 | // Re-adjust backreferences:
100 | // TODO What about \R backreferences (\0 isn't allowed, though)?
101 |
102 | // We assume that the expression is correct and therefore don't check
103 | // for matching parentheses.
104 |
105 | $number_of_captures = preg_match_all('/\([^?]|\(\?[^:]/', $sub_expr, $_);
106 |
107 | if ($number_of_captures === false)
108 | return false;
109 |
110 | if ($number_of_captures > 0) {
111 | $backref_expr = '/
112 | (?" : '?:';
135 | $new_expr = "($sub_name$sub_modifiers$sub_expr)";
136 | $result[] = $new_expr;
137 | }
138 |
139 | return '/' . implode($glue, $result) . '/';
140 | }
141 |
142 | /**
143 | * Strips a regular expression string off its delimiters and modifiers.
144 | * Additionally, normalizes the delimiters (i.e. reformats the pattern so that
145 | * it could have used "/" as delimiter).
146 | *
147 | * @param string $expression The regular expression string to strip.
148 | * @return array An array whose first entry is the expression itself, the
149 | * second an array of delimiters. If the argument is not a valid regular
150 | * expression, returns FALSE.
151 | *
152 | */
153 | function preg_strip($expression) {
154 | if (preg_match('/^(.)(.*)\\1([imsxeADSUXJu]*)$/s', $expression, $matches) !== 1)
155 | return false;
156 |
157 | $delim = $matches[1];
158 | $sub_expr = $matches[2];
159 | if ($delim !== '/') {
160 | // Replace occurrences by the escaped delimiter by its unescaped
161 | // version and escape new delimiter.
162 | $sub_expr = str_replace("\\$delim", $delim, $sub_expr);
163 | $sub_expr = str_replace('/', '\\/', $sub_expr);
164 | }
165 | $modifiers = $matches[3] === '' ? array() : str_split(trim($matches[3]));
166 |
167 | return array($sub_expr, $modifiers);
168 | }
169 |
170 | ?>
171 |
--------------------------------------------------------------------------------
/hyperlight/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Verdana;
3 | margin: 0;
4 | }
5 |
6 | #head h1, #content h2 {
7 | margin-top: 0;
8 | }
9 |
10 | #head {
11 | background: url(graphics/head-backback.png) repeat-x;
12 | color:#A67B5B;
13 | height:215px;
14 | }
15 |
16 | #head .text {
17 | background: transparent url(graphics/head-background.png) no-repeat 50% 0%;
18 | height: 100%;
19 | width: 100%;
20 | }
21 |
22 | #head .text h1 {
23 | margin: 0 auto;
24 | width: 600px;
25 | font: italic normal normal 36pt/215px Palatino, Times New Roman, serif;
26 | }
27 |
28 | #content {
29 | background: #BC7B57;
30 | }
31 |
32 | #swoosh {
33 | background: #BC7B57 url(graphics/body-background.png) no-repeat 50% 0%;
34 | width: 100%;
35 | height: 75px;
36 | position: absolute;
37 | z-index: 99;
38 | }
39 |
40 | #guide-menu {
41 | }
42 |
43 | #content .text {
44 | position: relative;
45 | padding: 1em 0;
46 | z-index: 100;
47 | }
48 |
49 | div.text {
50 | width: 600px;
51 | margin: 0 auto;
52 | }
53 |
54 | p {
55 | text-align: justify;
56 | }
57 |
58 | .source-code {
59 | border: 1px solid black;
60 | font-family: Consolas, Courier New;
61 | font-size: 0.9em;
62 | }
63 |
64 | pre {
65 | padding: 0.2em;
66 | }
67 |
68 | #switch-buttons {
69 | list-style: none;
70 | height: 2em;
71 | margin: 0;
72 | padding: 0;
73 | width: 100%;
74 | }
75 |
76 | #switch-buttons li {
77 | display: block;
78 | float: left;
79 | margin: 0.2em 0.4em 0.2em 0;
80 | padding: 0;
81 | width: 7em;
82 | }
83 |
84 | #switch-buttons li a {
85 | background: white;
86 | display: block;
87 | height: 2em;
88 | line-height: 2em;
89 | text-align: center;
90 | text-decoration: none;
91 | width: 100%;
92 | }
93 |
94 | #switch-buttons li a.active {
95 | background: black;
96 | }
97 |
--------------------------------------------------------------------------------
/hyperlight/test.php:
--------------------------------------------------------------------------------
1 | language()->name();
15 | $title = $file === $lang ?
16 | "
Test for language {$pretty_name}
" :
17 | "
Test with file “{$file}” for language {$pretty_name}
Look, ma: Inline code. Start off by writing ', 'cpp', 'code'); ?>
84 | at the beginning of your newly-created main.cpp file.
85 | Then you can insert the following code below:
86 |
89 |
Next, let's compile this code and execute it. This is done easily on the console: