Sparks: Assets, 1.5.1

http://getsparks.org/packages/assets/versions/HEAD/show

Creates our minified and combined .js and .css files
This commit is contained in:
Ismo Vuorinen
2013-07-11 07:46:29 +03:00
parent fabd8585ab
commit 7e73994e40
53 changed files with 20358 additions and 0 deletions

1
sparks/assets/1.5.1/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.DS_Store

View File

@@ -0,0 +1,98 @@
# Simple Assets Library
A simple assets library that has the ability to combine and minify your JavaScript and CSS assets.
Additionally there's a <a href="http://leafo.net/lessphp/">LessCSS</a> compiler and a <a href="https://github.com/alxlit/coffeescript-php">CoffeeScript</a> compiler.
## Third Party Libraries
The libraries <a href="https://github.com/rgrove/jsmin-php/">JSMin</a>, <a href="http://code.google.com/p/cssmin/">CSSMin</a>, <a href="http://leafo.net/lessphp/">LessPHP</a> and <a href="https://github.com/alxlit/coffeescript-php">CoffeeScript-PHP</a> are all created by third parties, but they're included in this package for convenience.
## Requirements
1. PHP 5.3+
2. CodeIgniter 2.1
3. Directory structure for the assets files, with a writeable cache directory
## Documentation
Set all your preferences in the config file (assets directories, options to minify and combine).
Now you can use the helper methods in your views like this:
<?php Assets::css(array('bootstrap.less', 'init.css', 'style.css')); ?>
<?php Assets::js(array('libs/jquery.js', 'script.js', 'bean.coffee')); ?>
You can load the javascript files from the CDN using:
<?php Assets::cdn(array('jquery','jquery-validate','jqueryui'));?>
There's also a method for clearing all cached files:
<?php Assets::clear_cache(); ?>
The default configuration assumes your assets directory is in the root of your project. Be sure to set the permissions for the cache directory so it can be writeable.
Note about "freeze" option: This basicly tells the lib not to scan the files and folders for new and changed files, but to pull all the info from the info.cache file. This speeds up the whole process a little bit. Useful for apps with a bigger load in production.
### LESS / CoffeeScript
Files with extensions .less and .coffee will automatically be processed through appropriate libraries.
### Groups
There's also a possibility to define groups of assets. This can be useful when for e.g. you want separate scripts in you page header, and others in the footer. This can be accomplished like this:
<?php Assets::js_group('head', array('libs/modernizr.js')); ?>
<?php Assets::js_group('footer', array('plugins.js', 'script.js')); ?>
The same thing will work with CSS files. You can use this to show groups of CSS files for specific pages:
<?php Assets::css_group('global', array('style.css')); ?>
<?php Assets::css_group('login', array('login.css')); ?>
### Importing CSS files (@import)
Including files via *@import* should work just fine, just be sure to use proper paths. Example of a stylesheet would be something like this:
@import "bootstrap/bootstrap.less";
@import "libs/fancybox.css";
body { background: #f2f2f2; }
Just keep in mind when using *@import* that those files are not scanned for changes and the cache wont be cleared in case you change a file that is included via *@import*.
### Images
A helper is also provided to display images from the directory setup in the config.
<?php echo Assets::img('logo.png'); ?>
You can also generate the img tag directly using a similar syntax as in the CodeIgniter HTML helper.
<?php echo Assets::img('logo.png', true, array('title' => 'Logo')); ?>
### Overriding CI base_url
By default Assets uses codeIgniter's `$config['base_url']` config to determine the URL for your assets. However this can be overwritten by defining the following configuration item:
```php
$config['assets']['base_url'] = 'https://example.com';
```
This will allow you to define your assets on a seperate static domain, or specify `https` for assets seperately from your CI application.
## Frameworks / Libraries
The library has been tested with Twitter Bootstrap 2.0.1 and HTML5 Boilerplate 3.0. It wont work with the latest Bootstrap 2.0.2 because of a problem in LessPHP. I hope this will be fixed soon. And if you happen to use the library with a different framework (bootstrap), give me a shout and I'll put it on this list. So here it is:
* Twitter Bootstrap 2.0.1 (2.0.2 not working yet) LESS
* HTML5 Boilerplate 3.0
## Directory structure example
/application
/assets
/cache
/css
/images
/js
/sparks
/system

View File

@@ -0,0 +1,48 @@
<?php
/*
|--------------------------------------------------------------------------
| Processing assets
|--------------------------------------------------------------------------
|
| Flags for processing actions
|
*/
$config['assets']['minify_css'] = true;
$config['assets']['minify_js'] = true;
$config['assets']['enable_less'] = true;
$config['assets']['enable_coffeescript'] = true;
$config['assets']['freeze'] = false;
/*
|--------------------------------------------------------------------------
| Cache
|--------------------------------------------------------------------------
|
| Define if the cache folder should be cleared when generating new cache files
|
*/
$config['assets']['auto_clear_cache'] = true;
$config['assets']['auto_clear_css_cache'] = true;
$config['assets']['auto_clear_js_cache'] = true;
/*
|--------------------------------------------------------------------------
| Default paths and directories, tags
|--------------------------------------------------------------------------
|
| Leave the base_url at null on default
| Default directories containing the assets
| Option to use HTML5 tags
|
*/
$config['assets']['base_url'] = null;
$config['assets']['assets_dir'] = 'assets';
$config['assets']['js_dir'] = 'js';
$config['assets']['css_dir'] = 'css';
$config['assets']['cache_dir'] = 'cache';
$config['assets']['img_dir'] = 'img';
$config['assets']['html5'] = true;

View File

@@ -0,0 +1,60 @@
<?php
$config['assets_cdn'] = array(
'jquery' => array(
'default_version' => '1.7.2',
'path' => 'https://ajax.googleapis.com/ajax/libs/jquery/%version%/jquery.min.js',
),
'jqueryui' => array(
'default_version' => '1.8.18',
'path' => 'https://ajax.googleapis.com/ajax/libs/jqueryui/%version%/jquery-ui.min.js',
),
'jquery-validate' => array(
'default_version' => '1.9',
'path' => 'http://ajax.aspnetcdn.com/ajax/jquery.validate/%version%/jquery.validate.min.js',
),
'jquery-mobile' => array(
'default_version' => '1.0.1',
'path' => 'http://ajax.aspnetcdn.com/ajax/jquery.mobile/%version%/jquery.mobile-%version%.min.js',
),
'jquery-mobile-css' => array(
'default_version' => '1.0.1',
'path' => 'http://ajax.aspnetcdn.com/ajax/jquery.mobile/%version%/jquery.mobile-%version%.min.css',
),
'jquery-cycle' => array(
'default_version' => '2.99',
'path' => 'http://ajax.aspnetcdn.com/ajax/jquery.cycle/%version%/jquery.cycle.all.min.js',
),
'chrome-frame' => array(
'default_version' => '1.0.2',
'path' => 'https://ajax.googleapis.com/ajax/libs/chrome-frame/%version%/CFInstall.min.js',
),
'ext-core' => array(
'default_version' => '3.1.0',
'path' => 'https://ajax.googleapis.com/ajax/libs/ext-core/%version%/ext-core.js',
),
'dojo' => array(
'default_version' => '1.7.2',
'path' => 'https://ajax.googleapis.com/ajax/libs/dojo/%version%/dojo/dojo.js',
),
'mootools' => array(
'default_version' => '1.4.5',
'path' => 'https://ajax.googleapis.com/ajax/libs/mootools/%version%/mootools-yui-compressed.js',
),
'prototype' => array(
'default_version' => '1.7.0.0',
'path' => 'https://ajax.googleapis.com/ajax/libs/prototype/%version%/prototype.js',
),
'scriptaculous' => array(
'default_version' => '1.9.0',
'path' => 'https://ajax.googleapis.com/ajax/libs/scriptaculous/%version%/scriptaculous.js',
),
'swfobject' => array(
'default_version' => '2.2',
'path' => 'https://ajax.googleapis.com/ajax/libs/swfobject/%version%/swfobject.js',
),
'webfont' => array(
'default_version' => '1.0.26',
'path' => 'https://ajax.googleapis.com/ajax/libs/webfont/%version%/webfont.js',
),
);

View File

@@ -0,0 +1,32 @@
<?php
/*
|--------------------------------------------------------------------------
| CssMin configuration
|--------------------------------------------------------------------------
|
| No need to change this if the defaults are ok for you
|
*/
$config['assets_cssmin_filters'] = array(
"ImportImports" => false,
"RemoveComments" => true,
"RemoveEmptyRulesets" => true,
"RemoveEmptyAtBlocks" => true,
"ConvertLevel3AtKeyframes" => false,
"ConvertLevel3Properties" => false,
"Variables" => true,
"RemoveLastDelarationSemiColon" => true,
);
$config['assets_cssmin_plugins'] = array(
"Variables" => true,
"ConvertFontWeight" => false,
"ConvertHslColors" => false,
"ConvertRgbColors" => false,
"ConvertNamedColors" => false,
"CompressColorValues" => false,
"CompressUnitValues" => false,
"CompressExpressionValues" => false
);

View File

@@ -0,0 +1,6 @@
<?php
// Load the assets library when loading the spark
$autoload['config'] = array('assets', 'assets_cssmin', 'assets_cdn');
$autoload['helper'] = array('url', 'file', 'directory', 'string', 'html', 'assets');
$autoload['libraries'] = array('assets');

View File

@@ -0,0 +1,67 @@
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* Assets Helper
* The function names are pretty self-explanatory
*
* @author Boris Strahija <boris@creolab.hr>
* @copyright Copyright (c) 2012, Boris Strahija, http://creolab.hr
* @version 1.5.0
*/
function assets_css($assets = null, $attributes = null)
{
Assets::css($assets, $attributes);
}
function assets_css_group($group = null, $assets = null, $attributes = null)
{
Assets::css_group($group, $assets, $attributes);
}
function assets_js($assets = null)
{
Assets::js($assets);
}
function assets_js_group($group = null, $assets = null)
{
Assets::js_group($group, $assets);
}
function assets_url($path = null)
{
return Assets::url($path);
}
function assets_img($path = null, $tag = false, $properties = null)
{
return Assets::img($path, $tag, $properties);
}
function assets_cdn($assets = null)
{
Assets::cdn($assets);
}
function assets_conditional($condition = null, $string = null)
{
Assets::conditional($condition, $string);
}
function clear_assets_cache($type = null)
{
Assets::clear_cache($type);
}
/* End of file assets_helper.php */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
<?php
namespace CoffeeScript;
Init::init();
/**
* @package CoffeeScript
* @author Alex Little
* @license MIT
* @homepage http://github.com/alxlit/coffeescript-php
*/
class Compiler {
/**
* Compile some CoffeeScript.
*
* Available options:
*
* 'filename' => The source file, for debugging (formatted into error messages)
* 'header' => Add a header to the generated source (default: TRUE)
* 'rewrite' => Enable rewriting token stream (debugging)
* 'tokens' => Reference to token stream (debugging)
* 'trace' => File to write parser trace to (debugging)
*
* @param string The source CoffeeScript code
* @param array Options (see above)
*
* @return string The resulting JavaScript (if there were no errors)
*/
static function compile($code, $options = array())
{
$lexer = new Lexer($code, $options);
if (isset($options['filename']))
{
Parser::$FILE = $options['filename'];
}
if (isset($options['tokens']))
{
$tokens = & $options['tokens'];
}
if (isset($options['trace']))
{
Parser::Trace(fopen($options['trace'], 'w', TRUE), '> ');
}
try
{
$parser = new Parser();
foreach (($tokens = $lexer->tokenize()) as $token)
{
$parser->parse($token);
}
$js = $parser->parse(NULL)->compile($options);
}
catch (\Exception $e)
{
throw new Error("In {$options['filename']}, ".$e->getMessage());
}
if ( ! isset($options['header']) || $options['header'])
{
$js = '// Generated by CoffeeScript PHP ' . VERSION . "\n" . $js;
}
return $js;
}
}
?>

View File

@@ -0,0 +1,15 @@
<?php
namespace CoffeeScript;
Init::init();
class Error extends \Exception
{
function __construct($message)
{
$this->message = $message;
}
}
?>

View File

@@ -0,0 +1,97 @@
<?php
namespace CoffeeScript;
Init::init();
class Helpers {
static function compact(array $array)
{
$compacted = array();
foreach ($array as $k => $v)
{
if ($v)
{
$compacted[] = $v;
}
}
return $compacted;
}
static function del( & $obj, $key)
{
$val = NULL;
if (isset($obj[$key]))
{
$val = $obj[$key];
unset($obj[$key]);
}
return $val;
}
static function extend($obj, $properties)
{
foreach ($properties as $k => $v)
{
$obj->{$k} = $v;
}
return $obj;
}
static function flatten(array $array)
{
$flattened = array();
foreach ($array as $k => $v)
{
if (is_array($v))
{
$flattened = array_merge($flattened, flatten($v));
}
else
{
$flattened[] = $v;
}
}
return $flattened;
}
static function & last( & $array, $back = 0)
{
static $NULL;
$i = count($array) - $back - 1;
if (isset($array[$i]))
{
return $array[$i];
}
else
{
// Make sure $NULL is really NULL.
$NULL = NULL;
return $NULL;
}
}
/**
* Wrap a primitive with an object, so that properties can be attached to it
* like in JavaScript.
*/
static function wrap($v)
{
return new Value($v);
}
}
?>

View File

@@ -0,0 +1,95 @@
<?php
namespace CoffeeScript;
define('VERSION', '1.3.1');
class Init {
/**
* Dummy function that doesn't actually do anything, it's just used to make
* sure that this file gets loaded.
*/
static function init() {}
/**
* This function may be used in lieu of an autoloader.
*/
static function load($root = NULL)
{
if ($root === NULL)
{
$root = realpath(dirname(__FILE__));
}
$files = array(
'Compiler',
'Error',
'Helpers',
'Lexer',
'Nodes',
'Parser',
'Rewriter',
'Scope',
'SyntaxError',
'Value',
'yy/Base', // load the base class first
'yy/While', // For extends While
'yy/Access',
'yy/Arr',
'yy/Assign',
'yy/Block',
'yy/Call',
'yy/Class',
'yy/Closure',
'yy/Code',
'yy/Comment',
'yy/Existence',
'yy/Extends',
'yy/For',
'yy/If',
'yy/In',
'yy/Index',
'yy/Literal',
'yy/Obj',
'yy/Op',
'yy/Param',
'yy/Parens',
'yy/Range',
'yy/Return',
'yy/Slice',
'yy/Splat',
'yy/Switch',
'yy/Throw',
'yy/Try',
'yy/Value',
);
foreach ($files as $file)
{
require_once "$root/$file.php";
}
}
}
//
// Function shortcuts. These are all used internally.
//
function compact(array $array) { return Helpers::compact($array); }
function del( & $obj, $key) { return Helpers::del($obj, $key); }
function extend($obj, $properties) { return Helpers::extend($obj, $properties); }
function flatten(array $array) { return Helpers::flatten($array); }
function & last( & $array, $back = 0) { return Helpers::last($array, $back); }
function wrap($v) { return Helpers::wrap($v); }
function t() { return call_user_func_array('CoffeeScript\Lexer::t', func_get_args()); }
function t_canonical() { return call_user_func_array('CoffeeScript\Lexer::t_canonical', func_get_args()); }
function multident($code, $tab) { return Nodes::multident($code, $tab); }
function unfold_soak($options, $parent, $name) { return Nodes::unfold_soak($options, $parent, $name); }
function utility($name) { return Nodes::utility($name); }
function yy() { return call_user_func_array('CoffeeScript\Nodes::yy', func_get_args()); }
?>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
<?php
namespace CoffeeScript;
Init::init();
define('LEVEL_TOP', 1);
define('LEVEL_PAREN', 2);
define('LEVEL_LIST', 3);
define('LEVEL_COND', 4);
define('LEVEL_OP', 5);
define('LEVEL_ACCESS', 6);
define('TAB', ' ');
define('IDENTIFIER_STR', '[$A-Za-z_\x7f-\x{ffff}][$\w\x7f-\x{ffff}]*');
define('IDENTIFIER', '/^'.IDENTIFIER_STR.'$/u');
define('IS_STRING', '/^[\'"]/');
define('SIMPLENUM', '/^[+-]?\d+$/');
define('METHOD_DEF', '/^(?:('.IDENTIFIER_STR.')\.prototype(?:\.('.IDENTIFIER_STR.')|\[("(?:[^\\\\"\r\n]|\\\\.)*"|\'(?:[^\\\\\'\r\n]|\\\\.)*\')\]|\[(0x[\da-fA-F]+|\d*\.?\d+(?:[eE][+-]?\d+)?)\]))|('.IDENTIFIER_STR.')$/u');
class Nodes {
static function multident($code, $tab)
{
$code = preg_replace('/\n/', "\n{$tab}", $code);
return preg_replace('/\s+$/', '', $code);
}
static function unfold_soak($options, $parent, $name)
{
if ( ! (isset($parent->{$name}) && $parent->{$name} && $ifn = $parent->{$name}->unfold_soak($options)))
{
return NULL;
}
$parent->{$name} = $ifn->body;
$ifn->body = yy('Value', $parent);
return $ifn;
}
static function utility($name)
{
Scope::$root->assign($ref = "__$name", call_user_func(array(__CLASS__, "utility_$name")));
return $ref;
}
static function utility_bind()
{
return 'function(fn, me){ return function(){ return fn.apply(me, arguments); }; }';
}
static function utility_extends()
{
return 'function(child, parent) { '
. 'for (var key in parent) { '
. 'if ('.self::utility('hasProp').'.call(parent, key)) '
. 'child[key] = parent[key]; '
. '} '
. 'function ctor() { '
. 'this.constructor = child; '
. '} '
. 'ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; '
. 'return child; '
. '}';
}
static function utility_hasProp()
{
return '{}.hasOwnProperty';
}
static function utility_indexOf()
{
return '[].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }';
}
static function utility_slice()
{
return '[].slice';
}
/**
* Since PHP can't return values from __construct, and some of the node
* classes rely heavily on this feature in JavaScript, we use this function
* instead of 'new'.
*/
static function yy($type)
{
$args = func_get_args();
array_shift($args);
$type = __NAMESPACE__.'\yy_'.$type;
$inst = new $type;
$inst = call_user_func_array(array($inst, 'constructor'), $args);
return $inst;
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,552 @@
<?php
namespace CoffeeScript;
Init::init();
class Rewriter
{
static $BALANCED_PAIRS = array(
array('(', ')'),
array('[', ']'),
array('{', '}'),
array('INDENT', 'OUTDENT'),
array('CALL_START', 'CALL_END'),
array('PARAM_START', 'PARAM_END'),
array('INDEX_START', 'INDEX_END'),
);
static $INVERSES = array();
static $EXPRESSION_START = array();
static $EXPRESSION_END = array();
static $EXPRESSION_CLOSE = array('CATCH', 'WHEN', 'ELSE', 'FINALLY');
static $IMPLICIT_FUNC = array('IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS');
static $IMPLICIT_CALL = array(
'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS',
'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'UNARY', 'SUPER',
'@', '->', '=>', '[', '(', '{', '--', '++'
);
static $IMPLICIT_UNSPACED_CALL = array('+', '-');
static $IMPLICIT_BLOCK = array('->', '=>', '{', '[', ',');
static $IMPLICIT_END = array('POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY', 'LOOP', 'TERMINATOR');
static $SINGLE_LINERS = array('ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN');
static $SINGLE_CLOSERS = array('TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN');
static $LINEBREAKS = array('TERMINATOR', 'INDENT', 'OUTDENT');
static $initialized = FALSE;
static function init()
{
if (self::$initialized) return;
self::$initialized = TRUE;
foreach (self::$BALANCED_PAIRS as $pair)
{
list($left, $rite) = $pair;
self::$EXPRESSION_START[] = self::$INVERSES[$rite] = $left;
self::$EXPRESSION_END[] = self::$INVERSES[$left] = $rite;
}
self::$EXPRESSION_CLOSE = array_merge(self::$EXPRESSION_CLOSE, self::$EXPRESSION_END);
}
function __construct($tokens)
{
self::init();
$this->tokens = $tokens;
}
function add_implicit_braces()
{
$stack = array();
$start = NULL;
$starts_line = NULL;
$same_line = TRUE;
$start_indent = 0;
$self = $this;
$condition = function( & $token, $i) use ( & $self, & $same_line, & $starts_line)
{
$list = array();
for ($j = 0; $j < 3; $j++)
{
$k = ($i + 1) + $j;
$list[$j] = isset($self->tokens[$k]) ? $self->tokens[$k] : array(NULL, NULL);
}
list($one, $two, $three) = $list;
if ($one[0] === t('HERECOMMENT'))
{
return FALSE;
}
$tag = $token[0];
if (in_array($tag, t(Rewriter::$LINEBREAKS)))
{
$same_line = FALSE;
}
return
( (in_array($tag, t('TERMINATOR', 'OUTDENT')) || (in_array($tag, t(Rewriter::$IMPLICIT_END)) && $same_line)) &&
( ( ! $starts_line && $self->tag($i - 1) !== t(',')) ||
! ($two[0] === t(':') || $one[0] === t('@') && $three[0] === t(':'))) ) ||
($tag === t(',') &&
! in_array($one[0], t('IDENTIFIER', 'NUMBER', 'STRING', '@', 'TERMINATOR', 'OUTDENT')) );
};
$action = function( & $token, $i) use ( & $self)
{
$tok = $self->generate(t('}'), '}', $token[2]);
array_splice($self->tokens, $i, 0, array($tok));
};
$this->scan_tokens(function( & $token, $i, & $tokens) use (& $self, & $stack, & $start, & $start_indent, & $condition, & $action, & $starts_line, & $same_line)
{
if (in_array(($tag = $token[0]), t(Rewriter::$EXPRESSION_START)))
{
$stack[] = array( ($tag === t('INDENT') && $self->tag($i - 1) === t('{')) ? t('{') : $tag, $i );
return 1;
}
if (in_array($tag, t(Rewriter::$EXPRESSION_END)))
{
$start = array_pop($stack);
return 1;
}
$len = count($stack) - 1;
if ( ! ($tag === t(':') && (($ago = $self->tag($i - 2)) === t(':') || ( ! isset($stack[$len]) || $stack[$len][0] !== t('{'))) ))
{
return 1;
}
$same_line = TRUE;
$stack[] = array(t('{'));
$idx = (isset($ago) && $ago === t('@')) ? $i - 2 : $i - 1;
while ($self->tag($idx - 2) === t('HERECOMMENT'))
{
$idx -= 2;
}
$prev_tag = $self->tag($idx - 1);
$starts_line = ! $prev_tag || in_array($prev_tag, t(Rewriter::$LINEBREAKS));
$value = wrap('{');
$value->generated = TRUE;
$tok = $self->generate(t('{'), $value, $token[2]);
array_splice($tokens, $idx, 0, array($tok));
$self->detect_end($i + 2, $condition, $action);
return 2;
});
}
function add_implicit_indentation()
{
$self = $this;
$starter = $indent = $outdent = NULL;
$condition = function($token, $i) use ( & $starter)
{
return $token[1] !== ';' && in_array($token[0], t(Rewriter::$SINGLE_CLOSERS)) && ! ($token[0] === t('ELSE') && ! in_array($starter, t('IF', 'THEN')));
};
$action = function($token, $i) use ( & $self, & $outdent)
{
if ($outdent !== NULL)
{
array_splice($self->tokens, $self->tag($i - 1) === t(',') ? $i - 1 : $i, 0, array($outdent));
}
};
$this->scan_tokens(function( & $token, $i, & $tokens) use ( & $action, & $condition, & $self, & $indent, & $outdent, & $starter)
{
$tag = $token[0];
if ($tag === t('TERMINATOR') && $self->tag($i + 1) === t('THEN'))
{
array_splice($tokens, $i, 1);
return 0;
}
if ($tag === t('ELSE') && $self->tag($i - 1) !== t('OUTDENT'))
{
array_splice($tokens, $i, 0, $self->indentation($token));
return 2;
}
if ($tag === t('CATCH') && in_array($self->tag($i + 2), t('OUTDENT', 'TERMINATOR', 'FINALLY')))
{
array_splice($tokens, $i + 2, 0, $self->indentation($token));
return 4;
}
if (in_array($tag, t(Rewriter::$SINGLE_LINERS)) && $self->tag($i + 1) !== t('INDENT') &&
! ($tag === t('ELSE') && $self->tag($i + 1) === t('IF')))
{
$starter = $tag;
list($indent, $outdent) = $self->indentation($token, TRUE);
if ($starter === t('THEN'))
{
$indent['fromThen'] = TRUE;
}
array_splice($tokens, $i + 1, 0, array($indent));
$self->detect_end($i + 2, $condition, $action);
if ($tag === t('THEN'))
{
array_splice($tokens, $i, 1);
}
return 1;
}
return 1;
});
}
function add_implicit_parentheses()
{
$no_call = $seen_single = $seen_control = FALSE;
$self = $this;
$condition = function( & $token, $i) use ( & $self, & $seen_single, & $seen_control, & $no_call)
{
$tag = $token[0];
if ( ! $seen_single && (isset($token['fromThen']) && $token['fromThen']))
{
return TRUE;
}
if (in_array($tag, t('IF', 'ELSE', 'CATCH', '->', '=>', 'CLASS')))
{
$seen_single = TRUE;
}
if (in_array($tag, t('IF', 'ELSE', 'SWITCH', 'TRY', '=')))
{
$seen_control = TRUE;
}
if (in_array($tag, t('.', '?.', '::')) && $self->tag($i - 1) === t('OUTDENT'))
{
return TRUE;
}
return ! (isset($token['generated']) && $token['generated']) && $self->tag($i - 1) !== t(',') &&
(in_array($tag, t(Rewriter::$IMPLICIT_END)) || ($tag === t('INDENT') && ! $seen_control)) &&
($tag !== t('INDENT') ||
( ! in_array($self->tag($i - 2), t('CLASS', 'EXTENDS')) && ! in_array($self->tag($i - 1), t(Rewriter::$IMPLICIT_BLOCK)) &&
! (($post = isset($self->tokens[$i + 1]) ? $self->tokens[$i + 1] : NULL) && (isset($post['generated']) && $post['generated']) && $post[0] === t('{'))));
};
$action = function( & $token, $i) use ( & $self)
{
array_splice($self->tokens, $i, 0, array($self->generate(t('CALL_END'), ')', isset($token[2]) ? $token[2] : NULL)));
};
$this->scan_tokens(function( & $token, $i, & $tokens) use ( & $condition, & $action, & $no_call, & $self, & $seen_control, & $seen_single )
{
$tag = $token[0];
if (in_array($tag, t('CLASS', 'IF', 'FOR', 'WHILE')))
{
$no_call = TRUE;
}
$prev = NULL;
if (isset($tokens[$i - 1]))
{
$prev = & $tokens[$i - 1];
}
$current = $tokens[$i];
$next = isset($tokens[$i + 1]) ? $tokens[$i + 1] : NULL;
$call_object = ! $no_call && $tag === t('INDENT') &&
$next && (isset($next['generated']) && $next['generated']) && $next[0] === t('{') &&
$prev && in_array($prev[0], t(Rewriter::$IMPLICIT_FUNC));
$seen_single = FALSE;
$seen_control = FALSE;
if (in_array($tag, t(Rewriter::$LINEBREAKS)))
{
$no_call = FALSE;
}
if ($prev && ! (isset($prev['spaced']) && $prev['spaced']) && $tag === t('?'))
{
$token['call'] = TRUE;
}
if (isset($token['fromThen']) && $token['fromThen'])
{
return 1;
}
if ( ! ($call_object || ($prev && (isset($prev['spaced']) && $prev['spaced'])) &&
( (isset($prev['call']) && $prev['call']) || in_array($prev[0], t(Rewriter::$IMPLICIT_FUNC)) ) &&
( in_array($tag, t(Rewriter::$IMPLICIT_CALL)) || ! ( (isset($token['spaced']) && $token['spaced']) ||
(isset($token['newLine']) && $token['newLine']) ) &&
in_array($tag, t(Rewriter::$IMPLICIT_UNSPACED_CALL)) )
))
{
return 1;
}
array_splice($tokens, $i, 0, array($self->generate(t('CALL_START'), '(', $token[2])));
$self->detect_end($i + 1, $condition, $action);
if ($prev[0] === t('?'))
{
$prev[0] = t('FUNC_EXIST');
}
return 2;
});
}
function close_open_calls()
{
$self = $this;
$condition = function($token, $i) use ( & $self)
{
return in_array($token[0], t(')', 'CALL_END')) || $token[0] === t('OUTDENT') &&
$self->tag($i - 1) === t(')');
};
$action = function($token, $i) use ( & $self)
{
$self->tokens[($token[0] === t('OUTDENT') ? $i - 1 : $i)][0] = t('CALL_END');
};
$this->scan_tokens(function($token, $i) use ( & $self, $condition, $action)
{
if ($token[0] === t('CALL_START'))
{
$self->detect_end($i + 1, $condition, $action);
}
return 1;
});
}
function close_open_indexes()
{
$condition = function($token, $i)
{
return in_array($token[0], t(']', 'INDEX_END'));
};
$action = function( & $token, $i)
{
$token[0] = t('INDEX_END');
};
$self = $this;
$this->scan_tokens(function($token, $i) use ( & $self, $condition, $action)
{
if ($token[0] === t('INDEX_START'))
{
$self->detect_end($i + 1, $condition, $action);
}
return 1;
});
}
function detect_end($i, $condition, $action)
{
$tokens = & $this->tokens;
$levels = 0;
while (isset($tokens[$i]))
{
$token = & $tokens[$i];
if ($levels === 0 && $condition($token, $i))
{
return $action($token, $i);
}
if ( ! $token || $levels < 0)
{
return $action($token, $i - 1);
}
if (in_array($token[0], t(Rewriter::$EXPRESSION_START)))
{
$levels++;
}
else if (in_array($token[0], t(Rewriter::$EXPRESSION_END)))
{
$levels--;
}
$i++;
}
return $i - 1;
}
function generate($tag, $value, $line)
{
return array($tag, $value, $line, 'generated' => TRUE);
}
function indentation($token, $implicit = FALSE)
{
$indent = array(t('INDENT'), 2, $token[2]);
$outdent = array(t('OUTDENT'), 2, $token[2]);
if ($implicit)
{
$indent['generated'] = $outdent['generated'] = TRUE;
}
return array($indent, $outdent);
}
function remove_leading_newlines()
{
$key = 0;
foreach ($this->tokens as $k => $token)
{
$key = $k;
$tag = $token[0];
if ($tag !== t('TERMINATOR'))
{
break;
}
}
if ($key)
{
array_splice($this->tokens, 0, $key);
}
}
function remove_mid_expression_newlines()
{
$self = $this;
$this->scan_tokens(function( & $token, $i, & $tokens) use ( & $self)
{
if ( ! ($token[0] === t('TERMINATOR') && in_array($self->tag($i + 1), t(Rewriter::$EXPRESSION_CLOSE))))
{
return 1;
}
array_splice($tokens, $i, 1);
return 0;
});
}
function rewrite()
{
$this->remove_leading_newlines();
$this->remove_mid_expression_newlines();
$this->close_open_calls();
$this->close_open_indexes();
$this->add_implicit_indentation();
$this->tag_postfix_conditionals();
$this->add_implicit_braces();
$this->add_implicit_parentheses();
return $this->tokens;
}
function scan_tokens($block)
{
$i = 0;
while (isset($this->tokens[$i]))
{
$i += $block($this->tokens[$i], $i, $this->tokens);
}
return TRUE;
}
function tag($i)
{
return isset($this->tokens[$i]) ? $this->tokens[$i][0] : NULL;
}
function tag_postfix_conditionals()
{
$original = NULL;
$self = $this;
$condition = function($token, $i)
{
return in_array($token[0], t('TERMINATOR', 'INDENT'));
};
$action = function($token, $i) use ( & $original, & $self)
{
if ($token[0] !== t('INDENT') || ((isset($token['generated']) && $token['generated']) && ! (isset($token['fromThen']) && $token['fromThen'])))
{
$self->tokens[$original][0] = t('POST_'.t_canonical($self->tokens[$original][0]));
// $original[0] = t('POST_'.t_canonical($original[0]));
}
};
$self = $this;
$this->scan_tokens(function( & $token, $i) use ( & $original, & $condition, & $action, & $self)
{
if ( ! ($token[0] === t('IF')))
{
return 1;
}
$original = $i;
// $original = & $token;
$self->detect_end($i + 1, $condition, $action);
return 1;
});
}
}
?>

View File

@@ -0,0 +1,196 @@
<?php
namespace CoffeeScript;
Init::init();
/**
* Lexical scope manager.
*/
class Scope
{
static $root = NULL;
public $shared = FALSE;
private $has_assignments = FALSE;
function __construct($parent, $expressions, $method)
{
$this->parent = $parent;
$this->expressions = $expressions;
$this->method = $method;
$this->variables = array(
array('name' => 'arguments', 'type' => 'arguments')
);
$this->positions = array();
if ( ! $this->parent)
{
self::$root = $this;
}
}
function add($name, $type, $immediate = FALSE)
{
$name = ''.$name;
if ($this->shared && ! $immediate)
{
return $this->parent->add($name, $type, $immediate);
}
if (isset($this->positions[$name]))
{
$this->variables[$this->positions[$name]]['type'] = $type;
}
else
{
$this->variables[] = array('name' => $name, 'type' => $type);
$this->positions[$name] = count($this->variables) - 1;
}
}
function assign($name, $value)
{
$this->add($name, array('value' => $value, 'assigned' => TRUE), TRUE);
$this->has_assignments = TRUE;
}
function assigned_variables()
{
$tmp = array();
foreach ($this->variables as $v)
{
$type = $v['type'];
if (is_array($type) && isset($type['assigned']) && $type['assigned'])
{
$tmp[] = "{$v['name']} = {$type['value']}";
}
}
return $tmp;
}
function check($name, $immediate = FALSE)
{
$name = ''.$name;
$found = !! $this->type($name);
if ($found || $immediate)
{
return $found;
}
return $this->parent ? $this->parent->check($name) : FALSE;
}
function declared_variables()
{
$real_vars = array();
$temp_vars = array();
foreach ($this->variables as $v)
{
if ($v['type'] === 'var')
{
if ($v['name']{0} === '_')
{
$temp_vars[] = $v['name'];
}
else
{
$real_vars[] = $v['name'];
}
}
}
asort($real_vars);
asort($temp_vars);
return array_merge($real_vars, $temp_vars);
}
function find($name, $options = array())
{
if ($this->check($name, $options))
{
return TRUE;
}
$this->add($name, 'var');
return FALSE;
}
function free_variable($name, $reserve = TRUE)
{
$index = 0;
while ($this->check(($temp = $this->temporary($name, $index))))
{
$index++;
}
if ($reserve)
{
$this->add($temp, 'var', TRUE);
}
return $temp;
}
function has_assignments()
{
return $this->has_assignments;
}
function has_declarations()
{
return !! count($this->declared_variables());
}
function parameter($name)
{
if ($this->shared && $this->parent->check($name, TRUE))
{
return;
}
$this->add($name, 'param');
}
function temporary($name, $index)
{
if (strlen($name) > 1)
{
return '_'.$name.($index > 1 ? $index - 1 : '');
}
else
{
$val = strval(base_convert($index + intval($name, 36), 10, 36));
$val = preg_replace('/\d/', 'a', $val);
return '_'.$val;
}
}
function type($name)
{
foreach ($this->variables as $v)
{
if ($v['name'] === $name)
{
return $v['type'];
}
}
return NULL;
}
}
?>

View File

@@ -0,0 +1,9 @@
<?php
namespace CoffeeScript;
Init::init();
class SyntaxError extends Error {}
?>

View File

@@ -0,0 +1,20 @@
<?php
namespace CoffeeScript;
Init::init();
class Value
{
function __construct($v)
{
$this->v = $v;
}
function __toString()
{
return $this->v;
}
}
?>

View File

@@ -0,0 +1,31 @@
<?php
namespace CoffeeScript;
class yy_Access extends yy_Base
{
public $children = array('name');
function constructor($name, $tag = NULL)
{
$this->name = $name;
$this->name->as_key = TRUE;
$this->soak = $tag === 'soak';
return $this;
}
function compile($options, $level = NULL)
{
$name = $this->name->compile($options);
return preg_match(IDENTIFIER, $name) ? ".{$name}" : "[{$name}]";
}
function is_complex()
{
return FALSE;
}
}
?>

View File

@@ -0,0 +1,69 @@
<?php
namespace CoffeeScript;
class yy_Arr extends yy_Base
{
public $children = array('objects');
function constructor($objs)
{
$this->objects = $objs ? $objs : array();
return $this;
}
function assigns($name)
{
foreach ($this->objects as $obj)
{
if ($obj->assigns($name))
{
return TRUE;
}
}
return FALSE;
}
function compile_node($options)
{
if ( ! count($options))
{
return '[]';
}
$options['indent'] .= TAB;
$objs = $this->filter_implicit_objects($this->objects);
if (($code = yy_Splat::compile_splatted_array($options, $objs)))
{
return $code;
}
$code = array();
foreach ($objs as $obj)
{
$code[] = $obj->compile($options);
}
$code = implode(', ', $code);
if (strpos($code, "\n") !== FALSE)
{
return "[\n{$options['indent']}{$code}\n{$this->tab}]";
}
else
{
return "[{$code}]";
}
}
function filter_implicit_objects()
{
return call_user_func_array(array(yy('Call'), __FUNCTION__), func_get_args());
}
}
?>

View File

@@ -0,0 +1,350 @@
<?php
namespace CoffeeScript;
class yy_Assign extends yy_Base
{
public $children = array('variable', 'value');
function constructor($variable, $value, $context = '', $options = NULL)
{
$this->variable = $variable;
$this->value = $value;
$this->context = $context;
$this->param = $options ? $options['param'] : NULL;
$this->subpattern = isset($options['subpattern']) ? $options['subpattern'] : NULL;
$tmp = $this->variable->unwrap_all();
$forbidden = in_array($name = isset($tmp->value) ? $tmp->value : NULL, Lexer::$STRICT_PROSCRIBED);
if ($forbidden && $this->context !== 'object')
{
throw new SyntaxError("variable name may not be $name");
}
return $this;
}
function assigns($name)
{
if ($this->context === 'object')
{
return $this->value->assigns($name);
}
else
{
return $this->variable->assigns($name);
}
}
function compile_conditional($options)
{
list($left, $right) = $this->variable->cache_reference($options);
if ( ! count($left->properties) && $left->base instanceof yy_Literal && $left->base->value !== 'this' && ! $options['scope']->check($left->base->value))
{
throw new Error('the variable "'.$this->left->base->value.'" can\'t be assigned with '.$this->context.' because it has not been defined.');
}
if (strpos($this->context, '?') > -1)
{
$options['isExistentialEquals'] = TRUE;
}
$tmp = yy('Op', substr($this->context, 0, -1), $left, yy('Assign', $right, $this->value, '='));
return $tmp->compile($options);
}
function compile_node($options)
{
if (($is_value = ($this->variable instanceof yy_Value)))
{
if ($this->variable->is_array() || $this->variable->is_object())
{
return $this->compile_pattern_match($options);
}
if ($this->variable->is_splice())
{
return $this->compile_splice($options);
}
if (in_array($this->context, array('||=', '&&=', '?='), TRUE))
{
return $this->compile_conditional($options);
}
}
$name = $this->variable->compile($options, LEVEL_LIST);
if ( ! $this->context)
{
if ( ! ( ($var_base = $this->variable->unwrap_all()) && $var_base->is_assignable()))
{
throw new SyntaxError('"'.$this->variable->compile($options).'" cannot be assigned.');
}
if ( ! (is_callable(array($var_base, 'has_properties')) && $var_base->has_properties()))
{
if ($this->param)
{
$options['scope']->add($name, 'var');
}
else
{
$options['scope']->find($name);
}
}
}
if ($this->value instanceof yy_Code && preg_match(METHOD_DEF, ''.$name, $match))
{
if (isset($match[1]) && $match[1] !== '')
{
$this->value->klass = $match[1];
}
foreach (range(2, 5) as $i)
{
if (isset($match[$i]) && $match[$i] !== '')
{
$this->value->name = $match[$i];
break;
}
}
}
$val = $this->value->compile($options, LEVEL_LIST);
if ($this->context === 'object')
{
return "{$name}: {$val}";
}
$val = $name.' '.($this->context ? $this->context : '=').' '.$val;
return $options['level'] <= LEVEL_LIST ? $val : "({$val})";
}
function compile_pattern_match($options)
{
$top = $options['level'] === LEVEL_TOP;
$value = $this->value;
$objects = $this->variable->base->objects;
if ( ! ($olen = count($objects)))
{
$code = $value->compile($options);
return $options['level'] >= LEVEL_OP ? "({$code})" : $code;
}
$is_object = $this->variable->is_object();
if ($top && $olen === 1 && ! (($obj = $objects[0]) instanceof yy_Splat))
{
if ($obj instanceof yy_Assign)
{
$idx = $obj->variable->base;
$obj = $obj->value;
}
else
{
if ($obj->base instanceof yy_Parens)
{
$tmp = yy('Value', $obj->unwrap_all());
list($obj, $idx) = $tmp->cache_reference($options);
}
else
{
if ($is_object)
{
$idx = $obj->this ? $obj->properties[0]->name : $obj;
}
else
{
$idx = yy('Literal', 0);
}
}
}
$acc = preg_match(IDENTIFIER, $idx->unwrap()->value);
$value = yy('Value', $value);
if ($acc)
{
$value->properties[] = yy('Access', $idx);
}
else
{
$value->properties[] = yy('Index', $idx);
}
$tmp = $obj->unwrap();
$tmp = isset($tmp->value) ? $tmp->value : NULL;
if (in_array($tmp, Lexer::$COFFEE_RESERVED))
{
throw new SyntaxError('assignment to a reserved word: '.$obj->compile($options).' = '.$value->compile($options));
}
return yy('Assign', $obj, $value, NULL, array('param' => $this->param))->compile($options, LEVEL_TOP);
}
$vvar = $value->compile($options, LEVEL_LIST);
$assigns = array();
$splat = FALSE;
if ( ! preg_match(IDENTIFIER, $vvar) || $this->variable->assigns($vvar))
{
$assigns[] = ($ref = $options['scope']->free_variable('ref')).' = '.$vvar;
$vvar = $ref;
}
foreach ($objects as $i => $obj)
{
$idx = $i;
if ($is_object)
{
if ($obj instanceof yy_Assign)
{
$idx = $obj->variable->base;
$obj = $obj->value;
}
else
{
if ($obj->base instanceof yy_Parens)
{
$tmp = yy('Value', $obj->unwrap_all());
list($obj, $idx) = $tmp->cache_reference($options);
}
else
{
$idx = $obj->this ? $obj->properties[0]->name : $obj;
}
}
}
if ( ! $splat && ($obj instanceof yy_Splat))
{
$name = $obj->name->unwrap()->value;
$obj = $obj->unwrap();
$val = "{$olen} <= {$vvar}.length ? ".utility('slice').".call({$vvar}, {$i}";
$ivar = 'undefined';
if (($rest = $olen - $i - 1))
{
$ivar = $options['scope']->free_variable('i');
$val .= ", {$ivar} = {$vvar}.length - {$rest}) : ({$ivar} = {$i}, [])";
}
else
{
$val .= ') : []';
}
$val = yy('Literal', $val);
$splat = "{$ivar}++";
}
else
{
$name = $obj->unwrap();
$name = isset($name->value) ? $name->value : NULL;
if ($obj instanceof yy_Splat)
{
$obj = $obj->name->compile($options);
throw new SyntaxError("multiple splats are disallowed in an assignment: {$obj}...");
}
if (is_numeric($idx))
{
$idx = yy('Literal', $splat ? $splat : $idx);
$acc = FALSE;
}
else
{
$acc = $is_object ? preg_match(IDENTIFIER, $idx->unwrap()->value) : 0;
}
$val = yy('Value', yy('Literal', $vvar), array($acc ? yy('Access', $idx) : yy('Index', $idx)));
}
if (isset($name) && $name && in_array($name, Lexer::$COFFEE_RESERVED))
{
throw new SyntaxError("assignment to a reserved word: ".$obj->compile($options).' = '.$val->compile($options));
}
$tmp = yy('Assign', $obj, $val, NULL, array('param' => $this->param, 'subpattern' => TRUE));
$assigns[] = $tmp->compile($options, LEVEL_TOP);
}
if ( ! ($top || $this->subpattern))
{
$assigns[] = $vvar;
}
$code = implode(', ', $assigns);
return $options['level'] < LEVEL_LIST ? $code : "({$code})";
}
function compile_splice($options)
{
$tmp = array_pop($this->variable->properties);
$from = $tmp->range->from;
$to = $tmp->range->to;
$exclusive = $tmp->range->exclusive;
$name = $this->variable->compile($options);
list($from_decl, $from_ref) = $from ? $from->cache($options, LEVEL_OP) : array('0', '0');
if ($to)
{
if (($from && $from->is_simple_number()) && $to->is_simple_number())
{
$to = intval($to->compile($options)) - intval($from_ref);
if ( ! $exclusive)
{
$to++;
}
}
else
{
$to = $to->compile($options, LEVEL_ACCESS).' - '. $from_ref;
if ( ! $exclusive)
{
$to .= ' + 1';
}
}
}
else
{
$to = '9e9';
}
list($val_def, $val_ref) = $this->value->cache($options, LEVEL_LIST);
$code = "[].splice.apply({$name}, [{$from_decl}, {$to}].concat({$val_def})), {$val_ref}";
return $options['level'] > LEVEL_TOP ? "({$code})" : $code;
}
function is_statement($options)
{
return isset($options['level']) && $options['level'] === LEVEL_TOP && $this->context && strpos($this->context, '?') > -1;
}
function unfold_soak($options)
{
return unfold_soak($options, $this, 'variable');
}
}
?>

View File

@@ -0,0 +1,295 @@
<?php
namespace CoffeeScript;
Init::init();
class yy_Base
{
public $as_key = FALSE;
public $children = array();
public $ctor = NULL;
public $exclusive = NULL;
public $expression = NULL;
public $from = NULL;
public $front = NULL;
public $namespaced = FALSE;
public $negated = FALSE;
public $no_return = FALSE;
public $proto = FALSE;
public $soak = FALSE;
public $this = NULL;
public $to = NULL;
function __construct() {}
function constructor() { return $this; }
function __toString()
{
return ''.$this->to_string();
}
function cache($options, $level = NULL, $reused = NULL)
{
if ( ! $this->is_complex())
{
$ref = $level ? $this->compile($options, $level) : $this;
return array($ref, $ref);
}
else
{
$ref = yy('Literal', $reused ? $reused : $options['scope']->free_variable('ref'));
$sub = yy('Assign', $ref, $this);
if ($level)
{
return array($sub->compile($options, $level), $ref->value);
}
else
{
return array($sub, $ref);
}
}
}
function compile($options, $level = NULL)
{
if (isset($level))
{
$options['level'] = $level;
}
if ( ! ($node = $this->unfold_soak($options)))
{
$node = $this;
}
$node->tab = $options['indent'];
if ($options['level'] === LEVEL_TOP || ! $node->is_statement($options))
{
return $node->compile_node($options);
}
return $node->compile_closure($options);
}
function compile_closure($options)
{
if ($this->jumps())
{
throw new SyntaxError('cannot use a pure statement in an expression.');
}
$options['sharedScope'] = TRUE;
$closure = yy_Closure::wrap($this);
return $closure->compile_node($options);
}
function compile_loop_reference($options, $name)
{
$src = $tmp = $this->compile($options, LEVEL_LIST);
if ( ! ( ($src === 0 || $src === '') || preg_match(IDENTIFIER, $src) &&
$options['scope']->check($src, TRUE)))
{
$src = ($tmp = $options['scope']->free_variable($name)).' = '.$src;
}
return array($src, $tmp);
}
function contains($pred)
{
$contains = FALSE;
if (is_string($pred))
{
$tmp = __NAMESPACE__.'\\'.$pred;
$pred = function($node) use ($tmp)
{
return call_user_func($tmp, $node);
};
}
$this->traverse_children(FALSE, function($node) use ( & $contains, & $pred)
{
if ($pred($node))
{
$contains = TRUE;
return FALSE;
}
});
return $contains;
}
function contains_type($type)
{
return ($this instanceof $type) || $this->contains(function($node) use ( & $type)
{
return $node instanceof $type;
});
}
function each_child($func)
{
if ( ! ($this->children))
{
return $this;
}
foreach ($this->children as $i => $attr)
{
if (isset($this->{$attr}) && $this->{$attr})
{
foreach (flatten(array($this->{$attr})) as $i => $child)
{
if ($func($child) === FALSE)
{
break 2;
}
}
}
}
return $this;
}
function invert()
{
return yy('Op', '!', $this);
}
function assigns()
{
return FALSE;
}
function is_assignable()
{
return FALSE;
}
function is_complex()
{
return TRUE;
}
function is_chainable()
{
return FALSE;
}
function is_object()
{
return FALSE;
}
function is_statement()
{
return FALSE;
}
function is_undefined()
{
return FALSE;
}
function jumps()
{
return FALSE;
}
function last_non_comment($list)
{
$i = count($list);
while ($i--)
{
if ( ! ($list[$i] instanceof yy_Comment))
{
return $list[$i];
}
}
return NULL;
}
function make_return($res = NULL)
{
$me = $this->unwrap_all();
if ($res)
{
return yy('Call', yy('Literal', "{$res}.push"), array($me));
}
else
{
return yy('Return', $me);
}
}
function to_string($idt = '', $name = NULL)
{
if ($name === NULL)
{
$name = get_class($this);
}
$tree = "\n{$idt}{$name}";
if ($this->soak)
{
$tree .= '?';
}
$this->each_child(function($node) use ($idt, & $tree)
{
$tree .= $node->to_string($idt.TAB);
});
return $tree;
}
function traverse_children($cross_scope, $func)
{
$this->each_child(function($child) use ($cross_scope, & $func)
{
if ($func($child) === FALSE)
{
return FALSE;
}
return $child->traverse_children($cross_scope, $func);
});
}
function unfold_soak($options)
{
return FALSE;
}
function unwrap()
{
return $this;
}
function unwrap_all()
{
$node = $this;
while ($node !== ($node = $node->unwrap()))
{
continue;
}
return $node;
}
}
?>

View File

@@ -0,0 +1,294 @@
<?php
namespace CoffeeScript;
class yy_Block extends yy_Base
{
public $children = array('expressions');
function constructor($nodes = array())
{
$this->expressions = compact(flatten($nodes));
return $this;
}
function compile($options, $level = NULL)
{
if (isset($options['scope']))
{
return parent::compile($options, $level);
}
else
{
return $this->compile_root($options);
}
}
function compile_node($options)
{
$this->tab = $options['indent'];
$top = $options['level'] === LEVEL_TOP;
$codes = array();
foreach ($this->expressions as $i => $node)
{
$node = $node->unwrap_all();
$node = ($tmp = $node->unfold_soak($options)) ? $tmp : $node;
if ($node instanceof yy_Block)
{
$codes[] = $node->compile_node($options);
}
else if ($top)
{
$node->front = TRUE;
$code = $node->compile($options);
if ( ! $node->is_statement($options))
{
$code = "{$this->tab}{$code};";
if ($node instanceof yy_Literal)
{
$code = "{$code}\n";
}
}
$codes[] = $code;
}
else
{
$codes[] = $node->compile($options, LEVEL_LIST);
}
}
if ($top)
{
if (isset($this->spaced) && $this->spaced)
{
return "\n".implode("\n\n", $codes)."\n";
}
else
{
return implode("\n", $codes);
}
}
$code = ($tmp = implode(', ', $codes)) ? $tmp : 'void 0';
if (count($codes) && $options['level'] >= LEVEL_LIST)
{
return "({$code})";
}
else
{
return $code;
}
}
function compile_root($options)
{
$options['indent'] = isset($options['bare']) && $options['bare'] ? '' : TAB;
$options['scope'] = new Scope(NULL, $this, NULL);
$options['level'] = LEVEL_TOP;
$this->spaced = TRUE;
$prelude = '';
if ( ! (isset($options['bare']) && $options['bare']))
{
$prelude_exps = array();
foreach ($this->expressions as $i => $exp)
{
if ( ! ($exp->unwrap() instanceof yy_Comment))
{
break;
}
$prelude_exps[] = $exp;
}
$rest = array_slice($this->expressions, count($prelude_exps));
$this->expressions = $prelude_exps;
if ($prelude_exps)
{
$prelude = $this->compile_node(array_merge($options, array('indent' => '')))."\n";
}
$this->expressions = $rest;
}
$code = $this->compile_with_declarations($options);
if (isset($options['bare']) && $options['bare'])
{
return $code;
}
return "{$prelude}(function() {\n{$code}\n}).call(this);\n";
}
function compile_with_declarations($options)
{
$code = $post = '';
foreach ($this->expressions as $i => & $expr)
{
$expr = $expr->unwrap();
if ( ! ($expr instanceof yy_Comment || $expr instanceof yy_Literal))
{
break;
}
}
$options = array_merge($options, array('level' => LEVEL_TOP));
if ($i)
{
$rest = array_splice($this->expressions, $i, count($this->expressions));
list($spaced, $this->spaced) = array(isset($this->spaced) && $this->spaced, FALSE);
list($code, $this->spaced) = array($this->compile_node($options), $spaced);
$this->expressions = $rest;
}
$post = $this->compile_node($options);
$scope = $options['scope'];
if ($scope->expressions === $this)
{
$declars = $scope->has_declarations();
$assigns = $scope->has_assignments();
if ($declars or $assigns)
{
if ($i)
{
$code .= "\n";
}
$code .= $this->tab.'var ';
if ($declars)
{
$code .= implode(', ', $scope->declared_variables());
}
if ($assigns)
{
if ($declars)
{
$code .= ",\n{$this->tab}".TAB;
}
$code .= implode(",\n{$this->tab}".TAB, $scope->assigned_variables());
}
$code .= ";\n";
}
}
return $code.$post;
}
function is_empty()
{
return ! count($this->expressions);
}
function is_statement($options)
{
foreach ($this->expressions as $i => $expr)
{
if ($expr->is_statement($options))
{
return TRUE;
}
}
return FALSE;
}
function jumps($options = array())
{
foreach ($this->expressions as $i => $expr)
{
if ($expr->jumps($options))
{
return $expr;
}
}
return FALSE;
}
function make_return($res = NULL)
{
$len = count($this->expressions);
while ($len--)
{
$expr = $this->expressions[$len];
if ( ! ($expr instanceof yy_Comment))
{
$this->expressions[$len] = $expr->make_return($res);
if ($expr instanceof yy_Return && ! $expr->expression)
{
return array_splice($this->expressions, $len, 1);
}
break;
}
}
return $this;
}
function pop()
{
return array_pop($this->expressions);
}
function push($node)
{
$this->expressions[] = $node;
return $this;
}
function unshift($node)
{
array_unshift($this->expressions, $node);
return $this;
}
function unwrap()
{
return count($this->expressions) === 1 ? $this->expressions[0] : $this;
}
static function wrap($nodes)
{
if ( ! is_array($nodes))
{
$nodes = array($nodes);
}
if (count($nodes) === 1 && $nodes[0] instanceof yy_Block)
{
return $nodes[0];
}
return yy('Block', $nodes);
}
}
?>

View File

@@ -0,0 +1,283 @@
<?php
namespace CoffeeScript;
class yy_Call extends yy_Base
{
public $children = array('variable', 'args');
private $is_new;
private $is_super;
function constructor($variable = NULL, $args = array(), $soak = FALSE)
{
$this->args = $args;
$this->is_new = FALSE;
$this->is_super = $variable === 'super';
$this->variable = $this->is_super() ? NULL : $variable;
$this->soak = $soak;
return $this;
}
function compile_node($options)
{
if ($this->variable)
{
$this->variable->front = $this->front;
}
if (($code = yy_Splat::compile_splatted_array($options, $this->args, TRUE)))
{
return $this->compile_splat($options, $code);
}
$args = $this->filter_implicit_objects($this->args);
$tmp = array();
foreach ($args as $arg)
{
$tmp[] = $arg->compile($options, LEVEL_LIST);
}
$args = implode(', ', $tmp);
if ($this->is_super())
{
return $this->super_reference($options).'.call(this'.($args ? ', '.$args : '').')';
}
else
{
return ($this->is_new() ? 'new ' : '').$this->variable->compile($options, LEVEL_ACCESS)."({$args})";
}
}
function compile_super($args, $options)
{
return $this->super_reference($options).'.call(this'.(count($args) ? ', ' : '').$args.')';
}
function compile_splat($options, $splat_args)
{
if ($this->is_super())
{
return $this->super_reference($options).'.apply(this, '.$splat_args.')';
}
if ($this->is_new())
{
$idt = $this->tab.TAB;
return
"(function(func, args, ctor) {\n"
. "{$idt}ctor.prototype = func.prototype;\n"
. "{$idt}var child = new ctor, result = func.apply(child, args), t = typeof result;\n"
. "{$idt}return t == \"object\" || t == \"function\" ? result || child : child;\n"
. "{$this->tab}})(".$this->variable->compile($options, LEVEL_LIST).", $splat_args, function(){})";
}
$base = yy('Value', $this->variable);
if (($name = array_pop($base->properties)) && $base->is_complex())
{
$ref = $options['scope']->free_variable('ref');
$fun = "($ref = ".$base->compile($options, LEVEL_LIST).')'.$name->compile($options).'';
}
else
{
$fun = $base->compile($options, LEVEL_ACCESS);
$fun = preg_match(SIMPLENUM, $fun) ? "($fun)" : $fun;
if ($name)
{
$ref = $fun;
$fun .= $name->compile($options);
}
else
{
$ref = NULL;
}
}
$ref = $ref === NULL ? 'null' : $ref;
return "{$fun}.apply({$ref}, {$splat_args})";
}
function is_new($set = NULL)
{
if ($set !== NULL)
{
$this->is_new = !! $set;
}
return $this->is_new;
}
function is_super()
{
return $this->is_super;
}
function filter_implicit_objects($list)
{
$nodes = array();
foreach ($list as $node)
{
if ( ! ($node->is_object() && $node->base->generated))
{
$nodes[] = $node;
continue;
}
$obj = NULL;
foreach ($node->base->properties as $prop)
{
if (($prop instanceof yy_Assign) || $prop instanceof yy_Comment)
{
if ( ! $obj)
{
$nodes[] = ($obj = yy('Obj', array(), TRUE));
}
$obj->properties[] = $prop;
}
else
{
$nodes[] = $prop;
$obj = NULL;
}
}
}
return $nodes;
}
function new_instance()
{
$base = isset($this->variable->base) ? $this->variable->base : $this->variable;
if (($base instanceof yy_Call) && ! $base->is_new())
{
$base->new_instance();
}
else
{
$this->is_new = TRUE;
}
return $this;
}
function super_reference($options)
{
$method = $options['scope']->method;
if ($method === NULL)
{
throw new SyntaxError('cannot call super outside of a function.');
}
$name = isset($method->name) ? $method->name : NULL;
if ($name === NULL)
{
throw new SyntaxError('cannot call super on an anonymous function.');
}
if (isset($method->klass) && $method->klass)
{
$accesses = array(yy('Access', yy('Literal', '__super__')));
if (isset($method->static) && $method->static)
{
$accesses[] = yy('Access', yy('Literal', 'constructor'));
}
$accesses[] = yy('Access', yy('Literal', $name));
return yy('Value', yy('Literal', $method->klass), $accesses)->compile($options);
}
else
{
return $name.'.__super__.constructor';
}
}
function unfold_soak($options)
{
if ($this->soak)
{
if ($this->variable)
{
if ($ifn = unfold_soak($options, $this, 'variable'))
{
return $ifn;
}
$tmp = yy('Value', $this->variable);
list($left, $rite) = $tmp->cache_reference($options);
}
else
{
$left = yy('Literal', $this->super_reference($options));
$rite = yy('Value', $left);
}
$rite = yy('Call', $rite, $this->args);
$rite->is_new($this->is_new());
$left = yy('Literal', 'typeof '.$left->compile($options).' === "function"');
return yy('If', $left, yy('Value', $rite), array('soak' => TRUE));
}
$call = $this;
$list = array();
while (TRUE)
{
if ($call->variable instanceof yy_Call)
{
$list[] = $call;
$call = $call->variable;
continue;
}
if ( ! ($call->variable instanceof yy_Value))
{
break;
}
$list[] = $call;
if ( ! (($call = $call->variable->base) instanceof yy_Call))
{
break;
}
}
foreach (array_reverse($list) as $call)
{
if (isset($ifn))
{
if ($call->variable instanceof yy_Call)
{
$call->variable = $ifn;
}
else
{
$call->variable->base = $ifn;
}
}
$ifn = unfold_soak($options, $call, 'variable');
}
return isset($ifn) ? $ifn : NULL;
}
}
?>

View File

@@ -0,0 +1,282 @@
<?php
namespace CoffeeScript;
class yy_Class extends yy_Base
{
public $children = array('variable', 'parent', 'body');
function constructor($variable = NULL, $parent = NULL, $body = NULL)
{
$this->variable = $variable;
$this->parent = $parent;
$this->body = $body === NULL ? yy('Block') : $body;
$this->body->class_body = TRUE;
$this->bound_funcs = array();
return $this;
}
function add_bound_functions($options)
{
if ($this->bound_funcs)
{
foreach ($this->bound_funcs as $bvar)
{
$lhs = yy('Value', yy('Literal', 'this'), array(yy('Access', $bvar)))->compile($options);
$this->ctor->body->unshift(yy('Literal', "{$lhs} = ".utility('bind')."({$lhs}, this)"));
}
}
}
function add_properties($node, $name, $options)
{
$props = array_slice($node->base->properties, 0);
$exprs = array();
while ($assign = array_shift($props))
{
if ($assign instanceof yy_Assign)
{
$base = $assign->variable->base;
$func = $assign->value;
$assign->context = NULL;
if ($base->value === 'constructor')
{
if ($this->ctor)
{
throw new Error('cannot define more than one constructor in a class');
}
if (isset($func->bound) && $func->bound)
{
throw new Error('cannot define a constructor as a bound functions');
}
if ($func instanceof yy_Code)
{
$assign = $this->ctor = $func;
}
else
{
$this->external_ctor = $options['scope']->free_variable('class');
$assign = yy('Assign', yy('Literal', $this->external_ctor), $func);
}
}
else
{
if (isset($assign->variable->this) && $assign->variable->this)
{
$func->static = TRUE;
if (isset($func->bound) && $func->bound)
{
$func->context = $name;
}
}
else
{
$assign->variable = yy('Value', yy('Literal', $name), array( yy('Access', yy('Literal', 'prototype')), yy('Access', $base) ));
if ($func instanceof yy_Code && isset($func->bound) && $func->bound)
{
$this->bound_funcs[] = $base;
$func->bound = FALSE;
}
}
}
}
$exprs[] = $assign;
}
return compact($exprs);
}
function compile_node($options)
{
$decl = $this->determine_name();
$name = $decl ? $decl : '_Class';
if (isset($name->reserved) && $name->reserved)
{
$name = '_'.$name;
}
$lname = yy('Literal', $name);
$this->hoist_directive_prologue();
$this->set_context($name);
$this->walk_body($name, $options);
$this->ensure_constructor($name);
$this->body->spaced = TRUE;
if ( ! ($this->ctor instanceof yy_Code))
{
array_unshift($this->body->expressions, $this->ctor);
}
if ($decl)
{
array_unshift($this->body->expressions, yy('Assign', yy('Value', yy('Literal', $name), array(yy('Access', yy('Literal', 'name')))), yy('Literal', "'{$name}'")));
}
$this->body->expressions[] = $lname;
$this->body->expressions = array_merge($this->directives, $this->body->expressions);
$this->add_bound_functions($options);
$call = yy_Closure::wrap($this->body);
if ($this->parent)
{
$this->super_class = yy('Literal', $options['scope']->free_variable('super', FALSE));
array_unshift($this->body->expressions, yy('Extends', $lname, $this->super_class));
$call->args[] = $this->parent;
if (isset($call->variable->params))
{
$params = & $call->variable->params;
}
else
{
$params = & $call->variable->base->params;
}
$params[] = yy('Param', $this->super_class);
}
$klass = yy('Parens', $call, TRUE);
if ($this->variable)
{
$klass = yy('Assign', $this->variable, $klass);
}
return $klass->compile($options);
}
function determine_name()
{
if ( ! (isset($this->variable) && $this->variable))
{
return NULL;
}
if (($tail = last($this->variable->properties)))
{
$decl = $tail instanceof yy_Access ? $tail->name->value : NULL;
}
else
{
$decl = $this->variable->base->value;
}
if (in_array($decl, Lexer::$STRICT_PROSCRIBED, TRUE))
{
throw new SyntaxError("variable name may not be $decl");
}
$decl = $decl ? (preg_match(IDENTIFIER, $decl) ? $decl : NULL) : NULL;
return $decl;
}
function ensure_constructor($name)
{
if ( ! (isset($this->ctor) && $this->ctor))
{
$this->ctor = yy('Code');
if ($this->parent)
{
$this->ctor->body->push(yy('Literal', "{$name}.__super__.constructor.apply(this, arguments)"));
}
if (isset($this->external_ctor) && $this->external_ctor)
{
$this->ctor->body->push(yy('Literal', "{$this->external_ctor}.apply(this, arguments)"));
}
$this->ctor->body->make_return();
array_unshift($this->body->expressions, $this->ctor);
}
$this->ctor->ctor = $this->ctor->name = $name;
$this->ctor->klass = NULL;
$this->ctor->no_return = TRUE;
}
function hoist_directive_prologue()
{
$index = 0;
$expressions = $this->body->expressions;
while (isset($expressions[$index]) && ($node = $expressions[$index]) && ( ($node instanceof yy_Comment) || ($node instanceof yy_Value) && $node->is_string() ))
{
$index++;
}
$this->directives = array_slice($expressions, 0, $index);
}
function set_context($name)
{
$this->body->traverse_children(FALSE, function($node) use ($name)
{
if (isset($node->class_body) && $node->class_body)
{
return FALSE;
}
if ($node instanceof yy_Literal && ''.$node->value === 'this')
{
$node->value = $name;
}
else if ($node instanceof yy_Code)
{
$node->klass = $name;
if ($node->bound)
{
$node->context = $name;
}
}
});
}
function walk_body($name, $options)
{
$self = $this;
$this->traverse_children(FALSE, function($child) use ($name, $options, & $self)
{
if ($child instanceof yy_Class)
{
return FALSE;
}
if ($child instanceof yy_Block)
{
foreach (($exps = $child->expressions) as $i => $node)
{
if ($node instanceof yy_Value && $node->is_object(TRUE))
{
$exps[$i] = $self->add_properties($node, $name, $options);
}
}
$child->expressions = $exps = flatten($exps);
}
});
}
}
?>

View File

@@ -0,0 +1,49 @@
<?php
namespace CoffeeScript;
class yy_Closure
{
static function wrap($expressions, $statement = NULL, $no_return = FALSE)
{
if ($expressions->jumps())
{
return $expressions;
}
$func = yy('Code', array(), yy_Block::wrap(array($expressions)));
$args = array();
if (($mentions_args = $expressions->contains('yy_Closure::literal_args')) ||
$expressions->contains('yy_Closure::literal_this'))
{
$meth = yy('Literal', $mentions_args ? 'apply' : 'call');
$args = array(yy('Literal', 'this'));
if ($mentions_args)
{
$args[] = yy('Literal', 'arguments');
}
$func = yy('Value', $func, array(yy('Access', $meth)));
}
$func->no_return = $no_return;
$call = yy('Call', $func, $args);
return $statement ? yy_Block::wrap(array($call)) : $call;
}
static function literal_args($node)
{
return ($node instanceof yy_Literal) && (''.$node->value === 'arguments') && ! $node->as_key;
}
static function literal_this($node)
{
return (($node instanceof yy_Literal) && (''.$node->value === 'this') && ! $node->as_key) ||
($node instanceof yy_Code && $node->bound);
}
}
?>

View File

@@ -0,0 +1,200 @@
<?php
namespace CoffeeScript;
class yy_Code extends yy_Base
{
public $children = array('params', 'body');
function constructor($params = NULL, $body = NULL, $tag = NULL)
{
$this->params = $params ? $params : array();
$this->body = $body ? $body : yy('Block');
$this->bound = $tag === 'boundfunc';
$this->context = $this->bound ? '_this' : NULL;
return $this;
}
function compile_node($options)
{
$options['scope'] = new Scope($options['scope'], $this->body, $this);
$options['scope']->shared = del($options, 'sharedScope');
$options['indent'] .= TAB;
unset($options['bare']);
unset($options['isExistentialEquals']);
$params = array();
$exprs = array();
foreach ($this->param_names() as $name)
{
if ( ! $options['scope']->check($name))
{
$options['scope']->parameter($name);
}
}
foreach ($this->params as $param)
{
if ($param->splat)
{
if (isset($param->name->value) && $param->name->value)
{
$options['scope']->add($param->name->value, 'var', TRUE);
}
$params = array();
foreach ($this->params as $p)
{
$params[] = $p->as_reference($options);
}
$splats = yy('Assign', yy('Value', yy('Arr', $params)), yy('Value', yy('Literal', 'arguments')));
break;
}
}
foreach ($this->params as $param)
{
if ($param->is_complex())
{
$val = $ref = $param->as_reference($options);
if (isset($param->value) && $param->value)
{
$val = yy('Op', '?', $ref, $param->value);
}
$exprs[] = yy('Assign', yy('Value', $param->name), $val, '=', array('param' => TRUE));
}
else
{
$ref = $param;
if ($param->value)
{
$lit = yy('Literal', $ref->name->value.' == null');
$val = yy('Assign', yy('Value', $param->name), $param->value, '=');
$exprs[] = yy('If', $lit, $val);
}
}
if ( ! (isset($splats) && $splats))
{
$params[] = $ref;
}
}
$was_empty = $this->body->is_empty();
if (isset($splats) && $splats)
{
array_unshift($exprs, $splats);
}
if ($exprs)
{
foreach (array_reverse($exprs) as $expr)
{
array_unshift($this->body->expressions, $expr);
}
}
foreach ($params as $i => $p)
{
$options['scope']->parameter(($params[$i] = $p->compile($options)));
}
$uniqs = array();
foreach ($this->param_names() as $name)
{
if (in_array($name, $uniqs))
{
throw new SyntaxError("multiple parameters named $name");
}
$uniqs[] = $name;
}
if ( ! ($was_empty || $this->no_return))
{
$this->body->make_return();
}
if ($this->bound)
{
if (isset($options['scope']->parent->method->bound) && $options['scope']->parent->method->bound)
{
$this->bound = $this->context = $options['scope']->parent->method->context;
}
else if ( ! (isset($this->static) && $this->static))
{
$options['scope']->parent->assign('_this', 'this');
}
}
$idt = $options['indent'];
$code = 'function';
if ($this->ctor)
{
$code .= ' '.$this->name;
}
$code .= '('.implode(', ', $params).') {';
if ( ! $this->body->is_empty())
{
$code .= "\n".$this->body->compile_with_declarations($options)."\n{$this->tab}";
}
$code .= '}';
if ($this->ctor)
{
return $this->tab.$code;
}
return ($this->front || $options['level'] >= LEVEL_ACCESS) ? "({$code})" : $code;
}
function param_names()
{
$names = array();
foreach ($this->params as $param)
{
$names = array_merge($names, (array) $param->names());
}
return $names;
}
function is_statement()
{
return !! $this->ctor;
}
function jumps()
{
return FALSE;
}
function traverse_children($cross_scope, $func)
{
if ($cross_scope)
{
return parent::traverse_children($cross_scope, $func);
}
return NULL;
}
}
?>

View File

@@ -0,0 +1,37 @@
<?php
namespace CoffeeScript;
class yy_Comment extends yy_Base
{
function constructor($comment)
{
$this->comment = $comment;
return $this;
}
function compile_node($options, $level = NULL)
{
$code = '/*'.multident($this->comment, $this->tab)."\n{$this->tab}*/\n";
if ($level === LEVEL_TOP || $options['level'] === LEVEL_TOP)
{
$code = $options['indent'].$code;
}
return $code;
}
function is_statement()
{
return TRUE;
}
function make_return()
{
return $this;
}
}
?>

View File

@@ -0,0 +1,42 @@
<?php
namespace CoffeeScript;
class yy_Existence extends yy_Base
{
public $children = array('expression');
function constructor($expression)
{
$this->expression = $expression;
return $this;
}
function compile_node($options = array())
{
$this->expression->front = $this->front;
$code = $this->expression->compile($options, LEVEL_OP);
if (preg_match(IDENTIFIER, $code) && ! $options['scope']->check($code))
{
list($cmp, $cnj) = $this->negated ? array('===', '||') : array('!==', '&&');
$code = "typeof {$code} {$cmp} \"undefined\" {$cnj} {$code} {$cmp} null";
}
else
{
$code = "{$code} ".($this->negated ? '==' : '!=').' null';
}
return (isset($options['level']) && $options['level'] <= LEVEL_COND) ? $code : "({$code})";
}
function invert()
{
$this->negated = ! $this->negated;
return $this;
}
}
?>

View File

@@ -0,0 +1,26 @@
<?php
namespace CoffeeScript;
class yy_Extends extends yy_Base
{
public $children = array('child', 'parent');
function constructor($child, $parent)
{
$this->child = $child;
$this->parent = $parent;
return $this;
}
function compile($options)
{
$tmp = yy('Call', yy('Value', yy('Literal', utility('extends'))),
array($this->child, $this->parent));
return $tmp->compile($options);
}
}
?>

View File

@@ -0,0 +1,248 @@
<?php
namespace CoffeeScript;
class yy_For extends yy_While
{
public $children = array('body', 'source', 'guard', 'step');
function constructor($body, $source)
{
$this->source = $source['source'];
$this->guard = isset($source['guard']) ? $source['guard'] : NULL;
$this->step = isset($source['step']) ? $source['step'] : NULL;
$this->name = isset($source['name']) ? $source['name'] : NULL;
$this->index = isset($source['index']) ? $source['index'] : NULL;
$this->body = yy_Block::wrap(array($body));
$this->own = (isset($source['own']) && $source['own']);
$this->object = (isset($source['object']) && $source['object']);
if ($this->object)
{
$tmp = $this->name;
$this->name = $this->index;
$this->index = $tmp;
}
if ($this->index instanceof yy_Value)
{
throw SyntaxError('index cannot be a pattern matching expression');
}
$this->range = $this->source instanceof yy_Value && $this->source->base instanceof yy_Range &&
! count($this->source->properties);
$this->pattern = $this->name instanceof yy_Value;
if ($this->range && $this->index)
{
throw SyntaxError('indexes do not apply to range loops');
}
if ($this->range && $this->pattern)
{
throw SyntaxError('cannot pattern match over range loops');
}
$this->returns = FALSE;
return $this;
}
function compile_node($options)
{
$body = yy_Block::wrap(array($this->body));
$last_jumps = last($body->expressions);
$last_jumps = $last_jumps ? $last_jumps->jumps() : FALSE;
if ($last_jumps && $last_jumps instanceof yy_Return)
{
$this->returns = FALSE;
}
if ($this->range)
{
$source = $this->source->base;
}
else
{
$source = $this->source;
}
$scope = $options['scope'];
$name = $this->name ? $this->name->compile($options, LEVEL_LIST) : FALSE;
$index = $this->index ? $this->index->compile($options, LEVEL_LIST) : FALSE;
if ($name && ! $this->pattern)
{
$scope->find($name, array('immediate' => TRUE));
}
if ($index)
{
$scope->find($index, array('immediate' => TRUE));
}
if ($this->returns)
{
$rvar = $scope->free_variable('results');
}
$ivar = $this->object ? $index : $scope->free_variable('i');
$kvar = $this->range ? ($name ? $name : ($index ? $index : $ivar)) : ($index ? $index : $ivar);
$kvar_assign = $kvar !== $ivar ? "{$kvar} = " : '';
if ($this->step && ! $this->range)
{
$stepvar = $scope->free_variable('step');
}
if ($this->pattern)
{
$name = $ivar;
}
$var_part = '';
$guard_part = '';
$def_part = '';
$idt1 = $this->tab.TAB;
if ($this->range)
{
$for_part = $source->compile(array_merge($options, array('index' => $ivar, 'name' => $name, 'step' => $this->step)));
}
else
{
$svar = $this->source->compile($options, LEVEL_LIST);
if (($name || $this->own) && ! preg_match(IDENTIFIER, $svar))
{
$ref = $scope->free_variable('ref');
$def_part = "{$this->tab}{$ref} = {$svar};\n";
$svar = $ref;
}
if ($name && ! $this->pattern)
{
$name_part = "{$name} = {$svar}[{$kvar}]";
}
if ( ! $this->object)
{
$lvar = $scope->free_variable('len');
$for_var_part = "{$kvar_assign}{$ivar} = 0, {$lvar} = {$svar}.length";
if ($this->step)
{
$for_var_part .= ", {$stepvar} = ".$this->step->compile($options, LEVEL_OP);
}
$step_part = $kvar_assign.($this->step ? "{$ivar} += {$stepvar}" : ($kvar !== $ivar ? "++{$ivar}" : "{$ivar}++"));
$for_part = "{$for_var_part}; {$ivar} < {$lvar}; {$step_part}";
}
}
if ($this->returns)
{
$result_part = "{$this->tab}{$rvar} = [];\n";
$return_result = "\n{$this->tab}return {$rvar};";
$body->make_return($rvar);
}
if ($this->guard)
{
if ($body->expressions)
{
array_unshift($body->expressions, yy('If', yy('Parens', $this->guard)->invert(), yy('Literal', 'continue')));
}
else
{
$body = yy_Block::wrap(array(yy('If', $this->guard, $body)));
}
}
if ($this->pattern)
{
array_unshift($body->expressions, yy('Assign', $this->name, yy('Literal', "{$svar}[{$kvar}]")));
}
$def_part .= $this->pluck_direct_call($options, $body);
if (isset($name_part) && $name_part)
{
$var_part = "\n{$idt1}{$name_part};";
}
if ($this->object)
{
$for_part = "{$kvar} in {$svar}";
if ($this->own)
{
$guard_part = "\n{$idt1}if (!".utility('hasProp').".call({$svar}, {$kvar})) continue;";
}
}
$body = $body->compile(array_merge($options, array('indent' => $idt1)), LEVEL_TOP);
if ($body)
{
$body = "\n{$body}\n";
}
return
"{$def_part}"
. (isset($result_part) ? $result_part : '')
. "{$this->tab}for ({$for_part}) {{$guard_part}{$var_part}{$body}{$this->tab}}"
. (isset($return_result) ? $return_result : '');
}
function pluck_direct_call($options, $body)
{
$defs = '';
foreach ($body->expressions as $idx => $expr)
{
$expr = $expr->unwrap_all();
if ( ! ($expr instanceof yy_Call))
{
continue;
}
$val = $expr->variable->unwrap_all();
if ( ! ( ($val instanceof yy_Code) ||
($val instanceof yy_Value) &&
(isset($val->base) && $val->base && ($val->base->unwrap_all() instanceof yy_Code) &&
count($val->properties) === 1 &&
isset($val->properties[0]->name) &&
in_array($val->properties[0]->name['value'], array('call', 'apply'), TRUE))))
{
continue;
}
$fn = (isset($val->base) && $val->base) ? $val->base->unwrap_all() : $val;
$ref = yy('Literal', $options['scope']->free_variable('fn'));
$base = yy('Value', $ref);
if (isset($val->base) && $val->base)
{
list($val->base, $base) = array($base, $val);
}
$body->expressions[$idx] = yy('Call', $base, $expr->args);
$tmp = yy('Assign', $ref, $fn);
$defs .= $this->tab.$tmp->compile($options, LEVEL_TOP).";\n";
}
return $defs;
}
}
?>

View File

@@ -0,0 +1,161 @@
<?php
namespace CoffeeScript;
class yy_If extends yy_Base
{
public $children = array('condition', 'body', 'else_body');
function constructor($condition, $body, $options = array())
{
$this->condition = (isset($options['type']) && $options['type'] === 'unless') ? $condition->invert() : $condition;
$this->body = $body;
$this->else_body = NULL;
$this->is_chain = FALSE;
$this->soak = isset($options['soak']) ? $options['soak'] : NULL;
return $this;
}
function add_else($else_body)
{
if ($this->is_chain())
{
$this->else_body_node()->add_else($else_body);
}
else
{
$this->is_chain = $else_body instanceof yy_If;
$this->else_body = $this->ensure_block($else_body);
}
return $this;
}
function body_node()
{
return $this->body ? $this->body->unwrap() : NULL;
}
function compile_node($options = array())
{
return $this->is_statement($options) ? $this->compile_statement($options) : $this->compile_expression($options);
}
function compile_expression($options)
{
$cond = $this->condition->compile($options, LEVEL_COND);
$body = $this->body_node()->compile($options, LEVEL_LIST);
$alt = ($tmp = $this->else_body_node()) ? $tmp->compile($options, LEVEL_LIST) : 'void 0';
$code = "{$cond} ? {$body} : {$alt}";
return (isset($options['level']) && $options['level'] > LEVEL_COND) ? "({$code})" : $code;
}
function compile_statement($options)
{
$child = del($options, 'chainChild');
$exeq = del($options, 'isExistentialEquals');
if ($exeq)
{
return yy('If', $this->condition->invert(), $this->else_body_node(), array('type' => 'if'))->compile($options);
}
$cond = $this->condition->compile($options, LEVEL_PAREN);
$options['indent'] .= TAB;
$body = $this->ensure_block($this->body);
$if_part = "if ({$cond}) {\n".$body->compile($options)."\n{$this->tab}}";
if ( ! $child)
{
$if_part = $this->tab.$if_part;
}
if ( ! $this->else_body)
{
return $if_part;
}
$ret = $if_part.' else ';
if ($this->is_chain())
{
$options['indent'] = $this->tab;
$options['chainChild'] = TRUE;
$ret .= $this->else_body->unwrap()->compile($options, LEVEL_TOP);
}
else
{
$ret .= "{\n".$this->else_body->compile($options, LEVEL_TOP)."\n{$this->tab}}";
}
return $ret;
}
function else_body_node()
{
return (isset($this->else_body) && $this->else_body) ? $this->else_body->unwrap() : NULL;
}
function ensure_block($node)
{
return $node instanceof yy_Block ? $node : yy('Block', array($node));
}
function is_chain()
{
return $this->is_chain;
}
function is_statement($options = array())
{
return (isset($options['level']) && $options['level'] === LEVEL_TOP) ||
$this->body_node()->is_statement($options) ||
(($tmp = $this->else_body_node()) && $tmp->is_statement($options));
}
function jumps($options = array())
{
$tmp = $this->body->jumps($options);
if ( ! $tmp && isset($this->else_body))
{
$tmp = $this->else_body->jumps($options);
}
return $tmp;
}
function make_return($res = NULL)
{
if ( ! (isset($this->else_body) && $this->else_body))
{
if ($res)
{
$this->else_body = yy('Block', array(yy('Literal', 'void 0')));
}
}
if ($this->body)
{
$this->body = yy('Block', array($this->body->make_return($res)));
}
if ($this->else_body)
{
$this->else_body = yy('Block', array($this->else_body->make_return($res)));
}
return $this;
}
function unfold_soak()
{
return $this->soak ? $this : FALSE;
}
}
?>

View File

@@ -0,0 +1,97 @@
<?php
namespace CoffeeScript;
class yy_In extends yy_Base
{
public $children = array('object', 'array');
function constructor($object = NULL, $array = NULL)
{
$this->array = $array;
$this->object = $object;
return $this;
}
function compile_node($options = array())
{
if ($this->array instanceof yy_Value && $this->array->is_array())
{
$has_splat = FALSE;
foreach ($this->array->base->objects as $obj)
{
if ($obj instanceof yy_Splat)
{
$has_splat = TRUE;
break;
}
}
if ( ! $has_splat)
{
return $this->compile_or_test($options);
}
}
return $this->compile_loop_test($options);
}
function compile_or_test($options)
{
if ( ! $this->array->base->objects)
{
return '!!'.$this->negated;
}
list($sub, $ref) = $this->object->cache($options, LEVEL_OP);
list($cmp, $cnj) = $this->negated ? array(' !== ', ' && ') : array(' === ', ' || ');
$tests = array();
foreach ($this->array->base->objects as $i => $item)
{
$tests[] = ($i ? $ref : $sub).$cmp.$item->compile($options, LEVEL_ACCESS);
}
if ( ! $tests)
{
// In JavaScript '' + false gives 'false', not so in PHP
return 'false';
}
$tests = implode($cnj, $tests);
return (isset($options['level']) && $options['level'] < LEVEL_OP) ? $tests : "({$tests})";
}
function compile_loop_test($options)
{
list($sub, $ref) = $this->object->cache($options, LEVEL_LIST);
$code = utility('indexOf').".call(".$this->array->compile($options, LEVEL_LIST).", {$ref}) "
.($this->negated ? '< 0' : '>= 0');
if ($sub === $ref)
{
return $code;
}
$code = $sub.', '.$code;
return (isset($options['level']) && $options['level'] < LEVEL_LIST) ? $code : "({$code})";
}
function invert()
{
$this->negated = ! $this->negated;
return $this;
}
function to_string($idt = '', $name = __CLASS__)
{
return parent::to_string($idt, $name.($this->negated ? '!' : ''));
}
}
?>

View File

@@ -0,0 +1,27 @@
<?php
namespace CoffeeScript;
class yy_Index extends yy_Base
{
public $children = array('index');
function constructor($index)
{
$this->index = $index;
return $this;
}
function compile($options)
{
return '['.$this->index->compile($options, LEVEL_PAREN).']';
}
function is_complex()
{
return $this->index->is_complex();
}
}
?>

View File

@@ -0,0 +1,96 @@
<?php
namespace CoffeeScript;
class yy_Literal extends yy_Base
{
public $is_undefined = FALSE;
function constructor($value)
{
$this->value = $value;
return $this;
}
function assigns($name)
{
return $name === $this->value;
}
function compile_node($options)
{
if ($this->is_undefined())
{
$code = $options['level'] >= LEVEL_ACCESS ? '(void 0)' : 'void 0';
}
else if ($this->value === 'this')
{
if ( (isset($options['scope']->method->bound) && $options['scope']->method->bound) )
{
$code = $options['scope']->method->context;
}
else
{
$code = $this->value;
}
}
else if (isset($this->value->reserved) && $this->value->reserved)
{
$code = '"'.$this->value.'"';
}
else
{
$code = ''.$this->value;
}
return $this->is_statement() ? "{$this->tab}{$code};" : $code;
}
function is_assignable()
{
return preg_match(IDENTIFIER, ''.$this->value);
}
function is_complex()
{
return FALSE;
}
function is_statement()
{
return in_array(''.$this->value, array('break', 'continue', 'debugger'), TRUE);
}
function is_undefined()
{
return $this->is_undefined;
}
function jumps($options = array())
{
if ($this->value === 'break' && ! ( (isset($options['loop']) && $options['loop']) || (isset($options['block']) && $options['block']) ))
{
return $this;
}
if ($this->value === 'continue' && ! (isset($options['loop']) && $options['loop']))
{
return $this;
}
return FALSE;
}
function make_return()
{
return $this->is_statement() ? $this : parent::make_return();
}
function to_string($idt = '', $name = __CLASS__)
{
return ' "'.$this->value.'"';
}
}
?>

View File

@@ -0,0 +1,126 @@
<?php
namespace CoffeeScript;
class yy_Obj extends yy_Base
{
public $children = array('properties');
function constructor($props, $generated = FALSE)
{
$this->generated = $generated;
$this->properties = $props ? $props : array();
$this->objects = $this->properties;
return $this;
}
function assigns($name)
{
foreach ($this->properties as $prop)
{
if ($prop->assigns($name))
{
return TRUE;
}
}
return FALSE;
}
function compile_node($options)
{
$props = $this->properties;
$prop_names = array();
foreach ($this->properties as $prop)
{
if ($prop->is_complex())
{
$prop = isset($prop->variable) ? $prop->variable : NULL;
}
if ($prop)
{
$prop_name = $prop->unwrap_all();
$prop_name = isset($prop_name->value) ? $prop_name->value.'' : NULL;
if (in_array($prop_name, $prop_names))
{
throw new SyntaxError('multiple object literal properties named "'.$prop_name.'"');
}
$prop_names[] = $prop_name;
}
}
if ( ! count($props))
{
return ($this->front ? '({})' : '{}');
}
if ($this->generated)
{
foreach ($props as $node)
{
if ($node instanceof yy_Value)
{
throw new Error('cannot have an implicit value in an implicit object');
}
}
}
$idt = $options['indent'] .= TAB;
$last_non_com = $this->last_non_comment($this->properties);
foreach ($props as $i => $prop)
{
if ($i === count($props) - 1)
{
$join = '';
}
else if ($prop === $last_non_com || $prop instanceof yy_Comment)
{
$join = "\n";
}
else
{
$join = ",\n";
}
$indent = $prop instanceof yy_Comment ? '' : $idt;
if ($prop instanceof yy_Value && (isset($prop->this) && $prop->this))
{
$prop = yy('Assign', $prop->properties[0]->name, $prop, 'object');
}
if ( ! ($prop instanceof yy_Comment))
{
if ( ! ($prop instanceof yy_Assign))
{
$prop = yy('Assign', $prop, $prop, 'object');
}
if (isset($prop->variable->base))
{
$prop->variable->base->as_key = TRUE;
}
else
{
$prop->variable->as_key = TRUE;
}
}
$props[$i] = $indent.$prop->compile($options, LEVEL_TOP).$join;
}
$props = implode('', $props);
$obj = '{'.($props ? "\n{$props}\n{$this->tab}" : '').'}';
return ($this->front ? "({$obj})" : $obj);
}
}
?>

View File

@@ -0,0 +1,292 @@
<?php
namespace CoffeeScript;
class yy_Op extends yy_Base
{
static $CONVERSIONS = array(
'==' => '===',
'!=' => '!==',
'of' => 'in'
);
static $INVERSIONS = array(
'!==' => '===',
'===' => '!=='
);
public $children = array('first', 'second');
public $operator = NULL;
public $invert = TRUE;
function constructor($op, $first, $second = NULL, $flip = NULL)
{
if ($op === 'in')
{
return yy('In', $first, $second);
}
if ($op === 'do')
{
return $this->generate_do($first);
}
if ($op === 'new')
{
if ($first instanceof yy_Call && ! (isset($first->do) && $first->do) && ! (isset($first->is_new) && $first->is_new))
{
return $first->new_instance();
}
if ($first instanceof yy_Code && $first->bound || (isset($first->do) && $first->do))
{
$first = yy('Parens', $first);
}
}
$this->operator = isset(self::$CONVERSIONS[$op]) ? self::$CONVERSIONS[$op] : $op;
$this->first = $first;
$this->second = $second;
$this->flip = !! $flip;
return $this;
}
function compile_chain($options)
{
$tmp = $this->first->second->cache($options);
$this->first->second = $tmp[0];
$shared = $tmp[1];
$fst = $this->first->compile($options, LEVEL_OP);
$code = "{$fst} ".($this->invert ? '&&' : '||').' '.$shared->compile($options).' '
.$this->operator.' '.$this->second->compile($options, LEVEL_OP);
return "({$code})";
}
function compile_existence($options)
{
if ($this->first->is_complex() && $options['level'] > LEVEL_TOP)
{
$ref = yy('Literal', $options['scope']->free_variable('ref'));
$fst = yy('Parens', yy('Assign', $ref, $this->first));
}
else
{
$fst = $this->first;
$ref = $fst;
}
$tmp = yy('If', yy('Existence', $fst), $ref, array('type' => 'if'));
$tmp->add_else($this->second);
return $tmp->compile($options);
}
function compile_node($options, $level = NULL)
{
$is_chain = $this->is_chainable() && $this->first->is_chainable();
if ( ! $is_chain)
{
$this->first->front = $this->front;
}
$tmp = $this->first->unwrap_all();
$tmp = isset($tmp->value) ? $tmp->value : NULL;
if ($this->operator === 'delete' && $options['scope']->check($tmp))
{
throw new SyntaxError('delete operand may not be argument or var');
}
if (in_array($this->operator, array('--', '++')) && in_array($tmp, Lexer::$STRICT_PROSCRIBED))
{
throw new SyntaxError('prefix increment/decrement may not have eval or arguments operand');
}
if ($this->is_unary())
{
return $this->compile_unary($options);
}
if ($is_chain)
{
return $this->compile_chain($options);
}
if ($this->operator === '?')
{
return $this->compile_existence($options);
}
$this->first->front = $this->front;
$code = $this->first->compile($options, LEVEL_OP).' '.$this->operator.' '
.$this->second->compile($options, LEVEL_OP);
return $options['level'] <= LEVEL_OP ? $code : "({$code})";
}
function compile_unary($options)
{
if ($options['level'] >= LEVEL_ACCESS)
{
return yy('Parens', $this)->compile($options);
}
$parts = array($op = $this->operator);
$plus_minus = in_array($op, array('+', '-'), TRUE);
if (in_array($op, array('new', 'typeof', 'delete'), TRUE) ||
$plus_minus &&
$this->first instanceof yy_Op && $this->first->operator === $op)
{
$parts[] = ' ';
}
if (($plus_minus && $this->first instanceof yy_Op) || ($op === 'new' && $this->first->is_statement($options)))
{
$this->first = yy('Parens', $this->first);
}
$parts[] = $this->first->compile($options, LEVEL_OP);
if ($this->flip)
{
$parts = array_reverse($parts);
}
return implode('', $parts);
}
function is_chainable()
{
return in_array($this->operator, array('<', '>', '>=', '<=', '===', '!=='), TRUE);
}
function is_complex()
{
return ! ($this->is_unary() && in_array($this->operator, array('+', '-'))) || $this->first->is_complex();
}
function invert()
{
if ($this->is_chainable() && $this->first->is_chainable())
{
$all_invertable = TRUE;
$curr = $this;
while ($curr && (isset($curr->operator) && $curr->operator))
{
if ($all_invertable)
{
$all_invertable = isset(self::$INVERSIONS[$curr->operator]);
}
$curr = $curr->first;
}
if ( ! $all_invertable)
{
return yy('Parens', $this)->invert();
}
$curr = $this;
while ($curr && (isset($curr->operator) && $curr->operator))
{
$curr->invert = ! $curr->invert;
$curr->operator = self::$INVERSIONS[$curr->operator];
$curr = $curr->first;
}
return $this;
}
else if (isset(self::$INVERSIONS[$this->operator]) && ($op = self::$INVERSIONS[$this->operator]))
{
$this->operator = $op;
if ($this->first->unwrap() instanceof yy_Op)
{
$this->first->invert();
}
return $this;
}
else if ($this->second)
{
return yy('Parens', $this)->invert();
}
else if ($this->operator === '!' && (($fst = $this->first->unwrap()) instanceof yy_Op) &&
in_array($fst->operator, array('!', 'in', 'instanceof'), TRUE))
{
return $fst;
}
else
{
return yy('Op', '!', $this);
}
}
function generate_do($exp)
{
$passed_params = array();
$func = $exp;
if ($exp instanceof yy_Assign && ($ref = $exp->value->unwrap()) instanceof yy_Code)
{
$func = $ref;
}
foreach ((isset($func->params) && $func->params ? $func->params : array()) as $param)
{
if (isset($param->value) && $param->value)
{
$passed_params[] = $param->value;
unset($param->value);
}
else
{
$passed_params[] = $param;
}
}
$call = yy('Call', $exp, $passed_params);
$call->do = TRUE;
return $call;
}
function is_simple_number()
{
return FALSE;
}
function is_unary()
{
return ! (isset($this->second) && $this->second);
}
function unfold_soak($options)
{
if (in_array($this->operator, array('++', '--', 'delete'), TRUE))
{
return unfold_soak($options, $this, 'first');
}
return NULL;
}
function to_string($idt = '', $name = __CLASS__)
{
return parent::to_string($idt, $name.' '.$this->operator);
}
}
?>

View File

@@ -0,0 +1,119 @@
<?php
namespace CoffeeScript;
class yy_Param extends yy_Base
{
public $children = array('name', 'value');
function constructor($name, $value = NULL, $splat = NULL)
{
$this->name = $name;
$this->value = $value;
$this->splat = $splat;
$name = $this->name->unwrap_all();
$name = isset($name->value) ? $name->value : NULL;
if (in_array($name, Lexer::$STRICT_PROSCRIBED))
{
throw new SyntaxError("parameter name \"$name\" is not allowed");
}
return $this;
}
function as_reference($options)
{
if (isset($this->reference) && $this->reference)
{
return $this->reference;
}
$node = $this->name;
if (isset($node->this) && $node->this)
{
$node = $node->properties[0]->name;
if (isset($this->value->reserved) && $this->value->reserved)
{
$node = yy('Literal', $options['scope']->free_variable($node->value));
}
}
else if ($node->is_complex())
{
$node = yy('Literal', $options['scope']->free_variable('arg'));
}
$node = yy('Value', $node);
if ($this->splat)
{
$node = yy('Splat', $node);
}
return ($this->reference = $node);
}
function compile($options, $level = NULL)
{
return $this->name->compile($options, LEVEL_LIST);
}
function is_complex()
{
return $this->name->is_complex();
}
function names($name = NULL)
{
if ($name === NULL)
{
$name = $this->name;
}
$at_param = function($obj)
{
$value = $obj->properties[0]->name;
return isset($value->reserved) && $value->reserved ? array() : array($value);
};
if ($name instanceof yy_Literal)
{
return array($name->value);
}
if ($name instanceof yy_Value)
{
return $at_param($name);
}
$names = array();
foreach ($name->objects as $obj)
{
if ($obj instanceof yy_Assign)
{
$names[] = $obj->variable->base->value;
}
else if ($obj->is_array() || $obj->is_object())
{
$names = array_merge($names, (array) $this->names($obj->base));
}
else if (isset($obj->this) && $obj->this)
{
$names = array_merge($names, (array) $at_param($obj));
}
else
{
$names[] = $obj->base->value;
}
}
return $names;
}
}
?>

View File

@@ -0,0 +1,45 @@
<?php
namespace CoffeeScript;
class yy_Parens extends yy_Base
{
public $children = array('body');
function constructor($body)
{
$this->body = $body;
return $this;
}
function compile_node($options = array())
{
$expr = $this->body->unwrap();
if ($expr instanceof yy_Value && $expr->is_atomic())
{
$expr->front = $this->front;
return $expr->compile($options);
}
$code = $expr->compile($options, LEVEL_PAREN);
$bare = $options['level'] < LEVEL_OP && ($expr instanceof yy_Op || $expr instanceof yy_Call ||
($expr instanceof yy_For && $expr->returns));
return $bare ? $code : "({$code})";
}
function is_complex()
{
return $this->body->is_complex();
}
function unwrap()
{
return $this->body;
}
}
?>

View File

@@ -0,0 +1,217 @@
<?php
namespace CoffeeScript;
class yy_Range extends yy_Base
{
public $children = array('from', 'to');
public $from_num = 0;
public $to_num = 0;
private static function check($num)
{
// '0' evaluates to FALSE in PHP, but TRUE in JavaScript. Explicit conditions here.
return ! in_array($num, array(0, NULL, FALSE, ''), TRUE);
}
function constructor($from, $to, $tag)
{
$this->from = $from;
$this->to = $to;
$this->exclusive = $tag === 'exclusive';
$this->equals = $this->exclusive ? '' : '=';
return $this;
}
function compile_array($options)
{
if (self::check($this->from_num) && self::check($this->to_num) && abs($this->from_num - $this->to_num) <= 20)
{
$range = range($this->from_num, $this->to_num);
if ($this->exclusive)
{
array_pop($range);
}
return '['.implode(', ', $range).']';
}
$idt = $this->tab.TAB;
$i = $options['scope']->free_variable('i');
$result = $options['scope']->free_variable('results');
$pre = "\n{$idt}{$result} = [];";
if (self::check($this->from_num) && self::check($this->to_num))
{
$options['index'] = $i;
$body = $this->compile_node($options);
}
else
{
$vars = "{$i} = {$this->from_c}".($this->to_c !== $this->to_var ? ", {$this->to_c}" : '');
$cond = "{$this->from_var} <= {$this->to_var}";
$body = "var {$vars}; {$cond} ? {$i} <{$this->equals} {$this->to_var} : {$i} >{$this->equals} {$this->to_var}; {$cond} ? {$i}++ : {$i}--";
}
$post = "{ {$result}.push({$i}); }\n{$idt}return {$result};\n{$options['indent']}";
$has_args = function($node)
{
return $node->contains(function($n)
{
return $n instanceof yy_Literal && $n->value === 'arguments' && ! $n->as_key();
});
return FALSE;
};
$args = '';
if ($has_args($this->from) || $has_args($this->to))
{
$args = ', arguments';
}
return "(function() {{$pre}\n{$idt}for ({$body}){$post}}).apply(this{$args})";
}
function compile_node($options)
{
if ( ! (isset($this->from_var) && $this->from_var))
{
$this->compile_variables($options);
}
if ( ! (isset($options['index']) && $options['index']))
{
return $this->compile_array($options);
}
$known = self::check($this->from_num) && self::check($this->to_num);
$idx = del($options, 'index');
$idx_name = del($options, 'name');
$named_index = $idx_name && $idx_name !== $idx;
$var_part = "{$idx} = {$this->from_c}";
if ($this->to_c !== $this->to_var)
{
$var_part .= ", {$this->to_c}";
}
if (isset($this->step) && $this->step !== $this->step_var)
{
$var_part .= ", {$this->step}";
}
list($lt, $gt) = array("{$idx} <{$this->equals}", "{$idx} >{$this->equals}");
if (isset($this->step_num) && self::check($this->step_num))
{
$cond_part = intval($this->step_num) > 0 ? "{$lt} {$this->to_var}" : "{$gt} {$this->to_var}";
}
else if ($known)
{
list($from, $to) = array(intval($this->from_num), intval($this->to_num));
$cond_part = $from <= $to ? "{$lt} {$to}" : "{$gt} {$to}";
}
else
{
$cond = "{$this->from_var} <= {$this->to_var}";
$cond_part = "{$cond} ? {$lt} {$this->to_var} : {$gt} {$this->to_var}";
}
if (isset($this->step_var) && $this->step_var)
{
$step_part = "{$idx} += {$this->step_var}";
}
else if ($known)
{
if ($named_index)
{
$step_part = $from <= $to ? "++{$idx}" : "--{$idx}";
}
else
{
$step_part = $from <= $to ? "{$idx}++" : "{$idx}--";
}
}
else
{
if ($named_index)
{
$step_part = "{$cond} ? ++{$idx} : --{$idx}";
}
else
{
$step_part = "{$cond} ? {$idx}++ : {$idx}--";
}
}
if ($named_index)
{
$var_part = "{$idx_name} = {$var_part}";
$step_part = "{$idx_name} = {$step_part}";
}
return "{$var_part}; {$cond_part}; {$step_part}";
}
function compile_simple($options)
{
list($from, $to) = array($this->from_num, $this->to_num);
$idx = del($options, 'index');
$step = del($options, 'step');
if ($step)
{
$stepvar = $options['scope']->free_variable('step');
}
$var_part = "{$idx} = {$from}";
if ($step)
{
$var_part .= ", {$stepvar} = ".$step->compile($options);
}
$cond_part = $from <= $to ? "{$idx} <{$this->equals} {$to}" : "{$idx} >{$this->equals} {$to}";
if ($step)
{
$step_part = "{$idx} += {$stepvar}";
}
else
{
$step_part = $from <= $to ? "{$idx}++" : "{$idx}--";
}
return "{$var_part}; {$cond_part}; {$step_part}";
}
function compile_variables($options)
{
$options = array_merge($options, array('top' => TRUE));
list($this->from_c, $this->from_var) = $this->from->cache($options, LEVEL_LIST);
list($this->to_c, $this->to_var) = $this->to->cache($options, LEVEL_LIST);
if ($step = del($options, 'step'))
{
list($this->step, $this->step_var) = $step->cache($options, LEVEL_LIST);
}
list($this->from_num, $this->to_num) = array(preg_match(SIMPLENUM, $this->from_var), preg_match(SIMPLENUM, $this->to_var));
if (isset($this->step_var) && $this->step_var)
{
$this->step_num = preg_match(SIMPLENUM, $this->step_var);
}
}
}
?>

View File

@@ -0,0 +1,56 @@
<?php
namespace CoffeeScript;
class yy_Return extends yy_Base
{
public $children = array('expression');
function constructor($expr = NULL)
{
if ($expr && ! ($expr->unwrap()->is_undefined()))
{
$this->expression = $expr;
}
return $this;
}
function compile($options, $level = NULL)
{
$expr = (isset($this->expression) && $this->expression) ? $this->expression->make_return() : NULL;
if ($expr && ! ($expr instanceof yy_Return))
{
$ret = $expr->compile($options, $level);
}
else
{
$ret = parent::compile($options, $level);
}
return $ret;
}
function compile_node($options)
{
return $this->tab.'return'.(isset($this->expression) && $this->expression ? ' '.$this->expression->compile($options, LEVEL_PAREN) : '').';';
}
function is_statement()
{
return TRUE;
}
function jumps()
{
return $this;
}
function make_return()
{
return $this;
}
}
?>

View File

@@ -0,0 +1,49 @@
<?php
namespace CoffeeScript;
class yy_Slice extends yy_Base
{
public $children = array('range');
function constructor($range)
{
parent::constructor();
$this->range = $range;
return $this;
}
function compile_node($options)
{
$to = $this->range->to;
$from = $this->range->from;
$from_str = $from ? $from->compile($options, LEVEL_PAREN) : '0';
$compiled = $to ? $to->compile($options, LEVEL_PAREN) : '';
if ($to && ! ( ! $this->range->exclusive && intval($compiled) === -1))
{
$to_str = ', ';
if ($this->range->exclusive)
{
$to_str .= $compiled;
}
else if (preg_match(SIMPLENUM, $compiled))
{
$to_str .= (intval($compiled) + 1);
}
else
{
$compiled = $to->compile($options, LEVEL_ACCESS);
$to_str .= "({$compiled} + 1) || 9e9";
}
}
return ".slice({$from_str}".(isset($to_str) ? $to_str : '').')';
}
}
?>

View File

@@ -0,0 +1,100 @@
<?php
namespace CoffeeScript;
class yy_Splat extends yy_Base
{
public $children = array('name');
static function compile_splatted_array($options, $list, $apply = FALSE)
{
$index = -1;
while (isset($list[++$index]) && ($node = $list[$index]) && ! ($node instanceof yy_Splat))
{
continue;
}
if ($index >= count($list))
{
return '';
}
if (count($list) === 1)
{
$code = $list[0]->compile($options, LEVEL_LIST);
if ($apply)
{
return $code;
}
return utility('slice').".call({$code})";
}
$args = array_slice($list, $index);
foreach ($args as $i => $node)
{
$code = $node->compile($options, LEVEL_LIST);
$args[$i] = ($node instanceof yy_Splat) ? utility('slice').".call({$code})" : "[{$code}]";
}
if ($index === 0)
{
return $args[0].'.concat('.implode(', ', array_slice($args, 1)).')';
}
$base = array();
foreach (array_slice($list, 0, $index) as $node)
{
$base[] = $node->compile($options, LEVEL_LIST);
}
return '['.implode(', ', $base).'].concat('.implode(', ', $args).')';
}
function constructor($name)
{
if (is_object($name))
{
$this->name = $name;
}
else
{
$this->name = yy('Literal', $name);
}
return $this;
}
function assigns($name)
{
return $this->name->assigns($name);
}
function compile($options)
{
if (isset($this->index) && $this->index)
{
return $this->compile_param($options);
}
else
{
return $this->name->compile($options);
}
}
function is_assignable()
{
return TRUE;
}
function unwrap()
{
return $this->name;
}
}
?>

View File

@@ -0,0 +1,121 @@
<?php
namespace CoffeeScript;
class yy_Switch extends yy_Base
{
public $children = array('subject', 'cases', 'otherwise');
function constructor($subject = NULL, $cases = array(), $otherwise = NULL)
{
$this->subject = $subject;
$this->cases = $cases;
$this->otherwise = $otherwise;
return $this;
}
function compile_node($options)
{
$idt1 = $options['indent'].TAB;
$idt2 = $options['indent'] = $idt1.TAB;
$code = $this->tab.'switch ('
.($this->subject ? $this->subject->compile($options, LEVEL_PAREN) : 'false')
.") {\n";
foreach ($this->cases as $i => $case)
{
list($conditions, $block) = $case;
foreach (flatten(array($conditions)) as $cond)
{
if ( ! $this->subject)
{
$cond = $cond->invert();
}
$code .= $idt1.'case '.$cond->compile($options, LEVEL_PAREN).":\n";
}
if ($body = $block->compile($options, LEVEL_TOP))
{
$code .= $body."\n";
}
if ($i === (count($this->cases) - 1) && ! $this->otherwise)
{
break;
}
$expr = $this->last_non_comment($block->expressions);
if ($expr instanceof yy_Return ||
($expr instanceof yy_Literal && $expr->jumps() && ''.$expr->value !== 'debugger'))
{
continue;
}
$code .= $idt2."break;\n";
}
if ($this->otherwise && count($this->otherwise->expressions))
{
$code .= $idt1."default:\n".$this->otherwise->compile($options, LEVEL_TOP)."\n";
}
return $code.$this->tab.'}';
}
function is_statement()
{
return TRUE;
}
function jumps($options = array())
{
if ( ! isset($options['block']))
{
$options['block'] = TRUE;
}
foreach ($this->cases as $case)
{
list($conds, $block) = $case;
if ($block->jumps($options))
{
return $block;
}
}
if (isset($this->otherwise) && $this->otherwise)
{
return $this->otherwise->jumps($options);
}
return FALSE;
}
function make_return($res = NULL)
{
foreach ($this->cases as $pair)
{
$pair[1]->make_return($res);
}
if ($res)
{
$this->otherwise = isset($this->otherwise) && $this->otherwise ? $this->otherwise : yy('Block', array(yy('Literal', 'void 0')));
}
if (isset($this->otherwise) && $this->otherwise)
{
$this->otherwise->make_return();
}
return $this;
}
}
?>

View File

@@ -0,0 +1,37 @@
<?php
namespace CoffeeScript;
class yy_Throw extends yy_Base
{
public $children = array('expression');
function constructor($expression)
{
$this->expression = $expression;
return $this;
}
function compile_node($options = array())
{
return $this->tab.'throw '.$this->expression->compile($options).';';
}
function is_statement()
{
return TRUE;
}
function jumps()
{
return FALSE;
}
function make_return()
{
return $this;
}
}
?>

View File

@@ -0,0 +1,79 @@
<?php
namespace CoffeeScript;
class yy_Try extends yy_Base
{
public $children = array('attempt', 'recovery', 'ensure');
function constructor($attempt = NULL, $error = NULL, $recovery = NULL, $ensure = NULL)
{
$this->attempt = $attempt;
$this->error = $error;
$this->recovery = $recovery;
$this->ensure = $ensure;
return $this;
}
function compile_node($options = array())
{
$options['indent'] .= TAB;
$error_part = $this->error ? ' ('.$this->error->compile($options).') ' : ' ';
$try_part = $this->attempt->compile($options, LEVEL_TOP);
$catch_part = '';
if ($this->recovery)
{
if (in_array($this->error, Lexer::$STRICT_PROSCRIBED))
{
throw new SyntaxError('catch variable may not be "'.$this->error->value.'"');
}
if ( ! $options['scope']->check($this->error->value))
{
$options['scope']->add($this->error->value, 'param');
}
$catch_part = " catch{$error_part}{\n".$this->recovery->compile($options, LEVEL_TOP)."\n{$this->tab}}";
}
else if ( ! ($this->ensure || $this->recovery))
{
$catch_part = ' catch (_error) {}';
}
$ensure_part = isset($this->ensure) && $this->ensure ? " finally {\n".$this->ensure->compile($options, LEVEL_TOP)."\n{$this->tab}}" : '';
return
"{$this->tab}try {\n"
. $try_part."\n"
. "{$this->tab}}{$catch_part}{$ensure_part}";
}
function is_statement()
{
return TRUE;
}
function jumps($options = array())
{
return $this->attempt->jumps($options) || (isset($this->recovery) && $this->recovery->jumps($options));
}
function make_return($res)
{
if ($this->attempt)
{
$this->attempt = $this->attempt->make_return($res);
}
if ($this->recovery)
{
$this->recovery = $this->recovery->make_return($res);
}
return $this;
}
}
?>

View File

@@ -0,0 +1,210 @@
<?php
namespace CoffeeScript;
class yy_Value extends yy_Base
{
public $children = array('base', 'properties');
function constructor($base = NULL, $props = NULL, $tag = NULL)
{
if ( ! $props && $base instanceof yy_Value)
{
return $base;
}
$this->base = $base;
$this->properties = $props ? $props : array();
if ($tag)
{
$this->{$tag} = TRUE;
}
return $this;
}
function add($prop)
{
$this->properties = array_merge($this->properties, is_object($prop) ? array($prop) : (array) $prop);
return $this;
}
function assigns($name)
{
return ! count($this->properties) && $this->base->assigns($name);
}
function cache_reference($options)
{
$name = last($this->properties);
if (count($this->properties) < 2 && ! $this->base->is_complex() && ! ($name && $name->is_complex()))
{
return array($this, $this);
}
$base = yy('Value', $this->base, array_slice($this->properties, 0, -1));
$bref = NULL;
if ($base->is_complex())
{
$bref = yy('Literal', $options['scope']->free_variable('base'));
$base = yy('Value', yy('Parens', yy('Assign', $bref, $base)));
}
if ( ! $name)
{
return array($base, $bref);
}
if ($name->is_complex())
{
$nref = yy('Literal', $options['scope']->free_variable('name'));
$name = yy('Index', yy('Assign', $nref, $name->index));
$nref = yy('Index', $nref);
}
$base->add($name);
return array($base, yy('Value', isset($bref) ? $bref : $base->base, array(isset($nref) ? $nref : $name)));
}
function compile_node($options)
{
$this->base->front = $this->front;
$props = $this->properties;
$code = $this->base->compile($options, count($props) ? LEVEL_ACCESS : NULL);
if ( (($this->base instanceof yy_Parens) || count($props)) && preg_match(SIMPLENUM, $code))
{
$code = $code.'.';
}
foreach ($props as $prop)
{
$code .= $prop->compile($options);
}
return $code;
}
function has_properties()
{
return !! count($this->properties);
}
function is_array()
{
return ! count($this->properties) && $this->base instanceof yy_Arr;
}
function is_assignable()
{
return $this->has_properties() || $this->base->is_assignable();
}
function is_atomic()
{
foreach (array_merge($this->properties, array($this->base)) as $node)
{
if ((isset($node->soak) && $node->soak) || $node instanceof yy_Call)
{
return FALSE;
}
}
return TRUE;
}
function is_complex()
{
return $this->has_properties() || $this->base->is_complex();
}
function is_object($only_generated = FALSE)
{
if (count($this->properties))
{
return FALSE;
}
return ($this->base instanceof yy_Obj) && ( ! $only_generated || $this->base->generated);
}
function is_simple_number()
{
return ($this->base instanceof yy_Literal) && preg_match(SIMPLENUM, ''.$this->base->value);
}
function is_splice()
{
return last($this->properties) instanceof yy_Slice;
}
function is_string()
{
return ($this->base instanceof yy_Literal) && preg_match(IS_STRING, ''.$this->base->value);
}
function is_statement($options)
{
return ! count($this->properties) && $this->base->is_statement($options);
}
function jumps($options = array())
{
return ! count($this->properties) && $this->base->jumps($options);
}
function unfold_soak($options)
{
if (isset($this->unfolded_soak))
{
return $this->unfolded_soak;
}
if (($ifn = $this->base->unfold_soak($options)))
{
$ifn->body->properties = array_merge($ifn->body->properties, $this->properties);
$result = $ifn;
}
else
{
foreach ($this->properties as $i => $prop)
{
if (isset($prop->soak) && $prop->soak)
{
$prop->soak = FALSE;
$fst = yy('Value', $this->base, array_slice($this->properties, 0, $i));
$snd = yy('Value', $this->base, array_slice($this->properties, $i));
if ($fst->is_complex())
{
$ref = yy('Literal', $options['scope']->free_variable('ref'));
$fst = yy('Parens', yy('Assign', $ref, $fst));
$snd->base = $ref;
}
$result = yy('If', yy('Existence', $fst), $snd, array('soak' => TRUE));
break;
}
}
}
$this->unfolded_soak = isset($result) ? $result : FALSE;
return $this->unfolded_soak;
}
function unwrap()
{
return count($this->properties) ? $this : $this->base;
}
}
?>

View File

@@ -0,0 +1,110 @@
<?php
namespace CoffeeScript;
class yy_While extends yy_Base
{
public $children = array('condition', 'guard', 'body');
public $returns = FALSE;
function constructor($condition = NULL, $options = NULL)
{
$this->condition = (isset($options['invert']) && $options['invert']) ?
$condition->invert() : $condition;
$this->guard = isset($options['guard']) ? $options['guard'] : NULL;
return $this;
}
function add_body($body)
{
$this->body = $body;
return $this;
}
function compile_node($options)
{
$options['indent'] .= TAB;
$set = '';
$body = $this->body;
if ($body->is_empty())
{
$body = '';
}
else
{
if ($this->returns)
{
$body->make_return($rvar = $options['scope']->free_variable('results'));
$set = "{$this->tab}{$rvar} = [];\n";
}
if ($this->guard)
{
if ($body->expressions)
{
array_unshift($body->expressions, yy('If', yy('Parens', $this->guard)->invert(), yy('Literal', 'continue')));
}
else
{
$body = yy_Block::wrap(array(yy('If', $this->guard, $body)));
}
}
$body = "\n".$body->compile($options, LEVEL_TOP)."\n{$this->tab}";
}
$code = $set.$this->tab.'while ('.$this->condition->compile($options, LEVEL_PAREN).") {{$body}}";
if ($this->returns)
{
$code .= "\n{$this->tab}return {$rvar};";
}
return $code;
}
function is_statement()
{
return TRUE;
}
function jumps()
{
$expressions = isset($this->body->expressions) ? $this->body->expressions : array();
if ( ! count($expressions))
{
return FALSE;
}
foreach ($expressions as $node)
{
if ($node->jumps(array('loop' => TRUE)))
{
return $node;
}
}
return FALSE;
}
function make_return($res = NULL)
{
if ($res)
{
return parent::make_return($res);
}
else
{
$this->returns = ! $this->jumps(array('loop' => TRUE));
}
return $this;
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,385 @@
<?php
/**
* jsmin.php - PHP implementation of Douglas Crockford's JSMin.
*
* This is pretty much a direct port of jsmin.c to PHP with just a few
* PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
* outputs to stdout, this library accepts a string as input and returns another
* string as output.
*
* PHP 5 or higher is required.
*
* Permission is hereby granted to use this version of the library under the
* same terms as jsmin.c, which has the following license:
*
* --
* Copyright (c) 2002 Douglas Crockford (www.crockford.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* The Software shall be used for Good, not Evil.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* --
*
* @package JSMin
* @author Ryan Grove <ryan@wonko.com>
* @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
* @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
* @copyright 2012 Adam Goforth <aag@adamgoforth.com> (Updates)
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 1.1.2 (2012-05-01)
* @link https://github.com/rgrove/jsmin-php
*/
class JSMin {
const ORD_LF = 10;
const ORD_SPACE = 32;
const ACTION_KEEP_A = 1;
const ACTION_DELETE_A = 2;
const ACTION_DELETE_A_B = 3;
protected $a = '';
protected $b = '';
protected $input = '';
protected $inputIndex = 0;
protected $inputLength = 0;
protected $lookAhead = null;
protected $output = '';
// -- Public Static Methods --------------------------------------------------
/**
* Minify Javascript
*
* @uses __construct()
* @uses min()
* @param string $js Javascript to be minified
* @return string
*/
public static function minify($js) {
$jsmin = new JSMin($js);
return $jsmin->min();
}
// -- Public Instance Methods ------------------------------------------------
/**
* Constructor
*
* @param string $input Javascript to be minified
*/
public function __construct($input) {
$this->input = str_replace("\r\n", "\n", $input);
$this->inputLength = strlen($this->input);
}
// -- Protected Instance Methods ---------------------------------------------
/**
* Action -- do something! What to do is determined by the $command argument.
*
* action treats a string as a single character. Wow!
* action recognizes a regular expression if it is preceded by ( or , or =.
*
* @uses next()
* @uses get()
* @throws JSMinException If parser errors are found:
* - Unterminated string literal
* - Unterminated regular expression set in regex literal
* - Unterminated regular expression literal
* @param int $command One of class constants:
* ACTION_KEEP_A Output A. Copy B to A. Get the next B.
* ACTION_DELETE_A Copy B to A. Get the next B. (Delete A).
* ACTION_DELETE_A_B Get the next B. (Delete B).
*/
protected function action($command) {
switch($command) {
case self::ACTION_KEEP_A:
$this->output .= $this->a;
case self::ACTION_DELETE_A:
$this->a = $this->b;
if ($this->a === "'" || $this->a === '"') {
for (;;) {
$this->output .= $this->a;
$this->a = $this->get();
if ($this->a === $this->b) {
break;
}
if (ord($this->a) <= self::ORD_LF) {
throw new JSMinException('Unterminated string literal.');
}
if ($this->a === '\\') {
$this->output .= $this->a;
$this->a = $this->get();
}
}
}
case self::ACTION_DELETE_A_B:
$this->b = $this->next();
if ($this->b === '/' && (
$this->a === '(' || $this->a === ',' || $this->a === '=' ||
$this->a === ':' || $this->a === '[' || $this->a === '!' ||
$this->a === '&' || $this->a === '|' || $this->a === '?' ||
$this->a === '{' || $this->a === '}' || $this->a === ';' ||
$this->a === "\n" )) {
$this->output .= $this->a . $this->b;
for (;;) {
$this->a = $this->get();
if ($this->a === '[') {
/*
inside a regex [...] set, which MAY contain a '/' itself. Example: mootools Form.Validator near line 460:
return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
*/
for (;;) {
$this->output .= $this->a;
$this->a = $this->get();
if ($this->a === ']') {
break;
} elseif ($this->a === '\\') {
$this->output .= $this->a;
$this->a = $this->get();
} elseif (ord($this->a) <= self::ORD_LF) {
throw new JSMinException('Unterminated regular expression set in regex literal.');
}
}
} elseif ($this->a === '/') {
break;
} elseif ($this->a === '\\') {
$this->output .= $this->a;
$this->a = $this->get();
} elseif (ord($this->a) <= self::ORD_LF) {
throw new JSMinException('Unterminated regular expression literal.');
}
$this->output .= $this->a;
}
$this->b = $this->next();
}
}
}
/**
* Get next char. Convert ctrl char to space.
*
* @return string|null
*/
protected function get() {
$c = $this->lookAhead;
$this->lookAhead = null;
if ($c === null) {
if ($this->inputIndex < $this->inputLength) {
$c = substr($this->input, $this->inputIndex, 1);
$this->inputIndex += 1;
} else {
$c = null;
}
}
if ($c === "\r") {
return "\n";
}
if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {
return $c;
}
return ' ';
}
/**
* Is $c a letter, digit, underscore, dollar sign, or non-ASCII character.
*
* @return bool
*/
protected function isAlphaNum($c) {
return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;
}
/**
* Perform minification, return result
*
* @uses action()
* @uses isAlphaNum()
* @uses get()
* @uses peek()
* @return string
*/
protected function min() {
if (0 == strncmp($this->peek(), "\xef", 1)) {
$this->get();
$this->get();
$this->get();
}
$this->a = "\n";
$this->action(self::ACTION_DELETE_A_B);
while ($this->a !== null) {
switch ($this->a) {
case ' ':
if ($this->isAlphaNum($this->b)) {
$this->action(self::ACTION_KEEP_A);
} else {
$this->action(self::ACTION_DELETE_A);
}
break;
case "\n":
switch ($this->b) {
case '{':
case '[':
case '(':
case '+':
case '-':
case '!':
case '~':
$this->action(self::ACTION_KEEP_A);
break;
case ' ':
$this->action(self::ACTION_DELETE_A_B);
break;
default:
if ($this->isAlphaNum($this->b)) {
$this->action(self::ACTION_KEEP_A);
}
else {
$this->action(self::ACTION_DELETE_A);
}
}
break;
default:
switch ($this->b) {
case ' ':
if ($this->isAlphaNum($this->a)) {
$this->action(self::ACTION_KEEP_A);
break;
}
$this->action(self::ACTION_DELETE_A_B);
break;
case "\n":
switch ($this->a) {
case '}':
case ']':
case ')':
case '+':
case '-':
case '"':
case "'":
$this->action(self::ACTION_KEEP_A);
break;
default:
if ($this->isAlphaNum($this->a)) {
$this->action(self::ACTION_KEEP_A);
}
else {
$this->action(self::ACTION_DELETE_A_B);
}
}
break;
default:
$this->action(self::ACTION_KEEP_A);
break;
}
}
}
return $this->output;
}
/**
* Get the next character, skipping over comments. peek() is used to see
* if a '/' is followed by a '/' or '*'.
*
* @uses get()
* @uses peek()
* @throws JSMinException On unterminated comment.
* @return string
*/
protected function next() {
$c = $this->get();
if ($c === '/') {
switch($this->peek()) {
case '/':
for (;;) {
$c = $this->get();
if (ord($c) <= self::ORD_LF) {
return $c;
}
}
case '*':
$this->get();
for (;;) {
switch($this->get()) {
case '*':
if ($this->peek() === '/') {
$this->get();
return ' ';
}
break;
case null:
throw new JSMinException('Unterminated comment.');
}
}
default:
return $c;
}
}
return $c;
}
/**
* Get next char. If is ctrl character, translate to a space or newline.
*
* @uses get()
* @return string|null
*/
protected function peek() {
$this->lookAhead = $this->get();
return $this->lookAhead;
}
}
// -- Exceptions ---------------------------------------------------------------
class JSMinException extends Exception {}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
name: assets
version: 1.5.1
compatibility: 2.1.0
tags: ["assets", "css", "javascript", "lesscss", "cofeescript"]