@ -10,16 +10,32 @@ var fs = require('fs'),
vm = require ( 'vm' ) ;
vm = require ( 'vm' ) ;
/ * *
/ * *
* A simple preprocessor that is based on the firefox preprocessor
* A simple preprocessor that is based on the Firefox preprocessor
* see ( https : //developer.mozilla.org/en/Build/Text_Preprocessor). The main
* ( https : //dxr.mozilla.org/mozilla-central/source/build/docs/preprocessor.rst).
* difference is that this supports a subset of the commands and it supports
* The main difference is that this supports a subset of the commands and it
* preproccesor commands in html style comments .
* supports preprocessor commands in HTML - style comments .
* Currently Supported commands :
*
* Currently supported commands :
* - if
* - if
* - elif
* - else
* - else
* - endif
* - endif
* - include
* - include
* - expand
* - expand
* - error
*
* Every # if must be closed with an # endif . Nested conditions are supported .
*
* Within an # if or # else block , one level of comment tokens is stripped . This
* allows us to write code that can run even without preprocessing . For example :
*
* //#if SOME_RARE_CONDITION
* // // Decrement by one
* // --i;
* //#else
* // // Increment by one.
* ++ i ;
* //#endif
* /
* /
function preprocess ( inFilename , outFilename , defines ) {
function preprocess ( inFilename , outFilename , defines ) {
// TODO make this really read line by line.
// TODO make this really read line by line.
@ -37,10 +53,28 @@ function preprocess(inFilename, outFilename, defines) {
function ( line ) {
function ( line ) {
out += line + '\n' ;
out += line + '\n' ;
} ) ;
} ) ;
function evaluateCondition ( code ) {
if ( ! code || ! code . trim ( ) ) {
throw new Error ( 'No JavaScript expression given at ' + loc ( ) ) ;
}
try {
return vm . runInNewContext ( code , defines , { displayErrors : false } ) ;
} catch ( e ) {
throw new Error ( 'Could not evaluate "' + code + '" at ' + loc ( ) + '\n' +
e . name + ': ' + e . message ) ;
}
}
function include ( file ) {
function include ( file ) {
var realPath = fs . realpathSync ( inFilename ) ;
var realPath = fs . realpathSync ( inFilename ) ;
var dir = path . dirname ( realPath ) ;
var dir = path . dirname ( realPath ) ;
try {
preprocess ( path . join ( dir , file ) , writeLine , defines ) ;
preprocess ( path . join ( dir , file ) , writeLine , defines ) ;
} catch ( e ) {
if ( e . code === 'ENOENT' ) {
throw new Error ( 'Failed to include "' + file + '" at ' + loc ( ) ) ;
}
throw e ; // Some other error
}
}
}
function expand ( line ) {
function expand ( line ) {
line = line . replace ( /__[\w]+__/g , function ( variable ) {
line = line . replace ( /__[\w]+__/g , function ( variable ) {
@ -53,52 +87,92 @@ function preprocess(inFilename, outFilename, defines) {
writeLine ( line ) ;
writeLine ( line ) ;
}
}
var s , state = 0 , stack = [ ] ;
// not inside if or else (process lines)
var STATE _NONE = 0 ;
// inside if, condition false (ignore until #else or #endif)
var STATE _IF _FALSE = 1 ;
// inside else, #if was false, so #else is true (process lines until #endif)
var STATE _ELSE _TRUE = 2 ;
// inside if, condition true (process lines until #else or #endif)
var STATE _IF _TRUE = 3 ;
// inside else, #if was true, so #else is false (ignore lines until #endif)
var STATE _ELSE _FALSE = 4 ;
var line ;
var state = STATE _NONE ;
var stack = [ ] ;
var control =
var control =
/^(?:\/\/|<!--)\s*#(if|else|endif|expand|include)(?:\s+(.*?)(?:-->)?$)?/ ;
/* jshint -W101 */
/^(?:\/\/|<!--)\s*#(if|elif|else|endif|expand|include|error)\b(?:\s+(.*?)(?:-->)?$)?/ ;
/* jshint +W101 */
var lineNumber = 0 ;
var lineNumber = 0 ;
while ( ( s = readLine ( ) ) !== null ) {
var loc = function ( ) {
return fs . realpathSync ( inFilename ) + ':' + lineNumber ;
} ;
while ( ( line = readLine ( ) ) !== null ) {
++ lineNumber ;
++ lineNumber ;
var m = control . exec ( s ) ;
var m = control . exec ( line ) ;
if ( m ) {
if ( m ) {
switch ( m [ 1 ] ) {
switch ( m [ 1 ] ) {
case 'if' :
case 'if' :
stack . push ( state ) ;
stack . push ( state ) ;
try {
state = evaluateCondition ( m [ 2 ] ) ? STATE _IF _TRUE : STATE _IF _FALSE ;
state = vm . runInNewContext ( m [ 2 ] , defines ) ? 3 : 1 ;
break ;
} catch ( e ) {
case 'elif' :
console . error ( 'Could not evalute line \'' + m [ 2 ] + '\' at ' +
if ( state === STATE _IF _TRUE ) {
fs . realpathSync ( inFilename ) + ':' + lineNumber ) ;
state = STATE _ELSE _FALSE ;
throw e ;
} else if ( state === STATE _IF _FALSE ) {
state = evaluateCondition ( m [ 2 ] ) ? STATE _IF _TRUE : STATE _IF _FALSE ;
} else if ( state === STATE _ELSE _TRUE || state === STATE _ELSE _FALSE ) {
throw new Error ( 'Found #elif after #else at ' + loc ( ) ) ;
} else {
throw new Error ( 'Found #elif without matching #if at ' + loc ( ) ) ;
}
}
break ;
break ;
case 'else' :
case 'else' :
state = state === 1 ? 3 : 2 ;
if ( state === STATE _IF _TRUE ) {
state = STATE _ELSE _FALSE ;
} else if ( state === STATE _IF _FALSE ) {
state = STATE _ELSE _TRUE ;
} else {
throw new Error ( 'Found #else without matching #if at ' + loc ( ) ) ;
}
break ;
break ;
case 'endif' :
case 'endif' :
if ( state === STATE _NONE ) {
throw new Error ( 'Found #endif without #if at ' + loc ( ) ) ;
}
state = stack . pop ( ) ;
state = stack . pop ( ) ;
break ;
break ;
case 'expand' :
case 'expand' :
if ( state === 0 || state === 3 ) {
if ( state !== STATE _IF _FALSE && state !== STATE _ELSE _FALSE ) {
expand ( m [ 2 ] ) ;
expand ( m [ 2 ] ) ;
}
}
break ;
break ;
case 'include' :
case 'include' :
if ( state === 0 || state === 3 ) {
if ( state !== STATE _IF _FALSE && state !== STATE _ELSE _FALSE ) {
include ( m [ 2 ] ) ;
include ( m [ 2 ] ) ;
}
}
break ;
break ;
case 'error' :
if ( state !== STATE _IF _FALSE && state !== STATE _ELSE _FALSE ) {
throw new Error ( 'Found #error ' + m [ 2 ] + ' at ' + loc ( ) ) ;
}
break ;
}
}
} else {
} else {
if ( state === 0 ) {
if ( state === STATE _NONE ) {
writeLine ( s ) ;
writeLine ( line ) ;
} else if ( state === 3 ) {
} else if ( ( state === STATE _IF _TRUE || state === STATE _ELSE _TRUE ) &&
writeLine ( s . replace ( /^\/\/|^<!--|-->/g , ' ' ) ) ;
stack . indexOf ( STATE _IF _FALSE ) === - 1 &&
stack . indexOf ( STATE _ELSE _FALSE ) === - 1 ) {
writeLine ( line . replace ( /^\/\/|^<!--|-->$/g , ' ' ) ) ;
}
}
}
}
}
}
if ( state !== 0 || stack . length !== 0 ) {
if ( state !== STATE _NONE || stack . length !== 0 ) {
throw new Error ( 'Missing endif in preprocessor.' ) ;
throw new Error ( 'Missing #endif in preprocessor for ' +
fs . realpathSync ( inFilename ) ) ;
}
}
if ( typeof outFilename !== 'function' ) {
if ( typeof outFilename !== 'function' ) {
fs . writeFileSync ( outFilename , out ) ;
fs . writeFileSync ( outFilename , out ) ;