Preprocessor class
class Preprocessor {
String _type;
Function _pathResolver;
MultiRegExp _multiRegExp;
static final List<String> types = [
'js', 'php', 'dart',
'css', 'less', 'styl', 'scss',
];
static final List<String> singleLinesSupportedTypes = [
'js', 'php', 'dart',
'less', 'styl', 'scss'
];
static final int errorSourceAhead = 50;
static final RegExp regExpIfThen = new RegExp(r'^(.*) then (.*)$');
Preprocessor(String this._type, [ Function this._pathResolver ]){
var EXPR_INSTRUCTIONS = '(include(?:Once)?|ifn?def|ifelse|if|\/if|endif|else|el(?:se)?if|eval|value|val|setbasedir)';
var multilineRegExp = new RegExp(r'(^[ \t]*)?\/\*[ ]*#[ ]*' + EXPR_INSTRUCTIONS + r'([^\*]*)[ ]*\*\/', multiLine:true);
var singlelineRegExp = new RegExp(r'(^[ \t]*)(?:\/\/)?#' + EXPR_INSTRUCTIONS + r'(.*)$', multiLine:true);
Set<RegExp> regExps = new Set();
regExps.add(multilineRegExp);
if (singleLinesSupportedTypes.contains(_type)) {
regExps.add(singlelineRegExp);
}
if (this._type == 'js') {
regExps.add(new RegExp(r'''(^[ \t]*)?(include(?:Once)?)\('([^\)]*)'\)'''));
}
_multiRegExp = new MultiRegExp.fromIterable(regExps);
}
static String indent(String str, String indent){
return str.split("\n").map((line) => indent + line).join("\n");
}
// TODO : process String instead of data. lines by lines. For multi lines things like if/else/elseif, we know if they should be included or not because we know if the condition is valid.
Future<String> process(Map<String, dynamic> defines, String data) {
assert(data != null);
MutableString mutableData = new MutableString(data);
Completer<String> completerPreprocessor = new Completer();
Queue stack = new Queue(); // Queue is too sophisticated
Future.forEach(mutableData.allMatchesFromMultiRegExp(this._multiRegExp), (MutableStringMatch match){
var completer = new Completer<String>();
String indent = match[1], instruction=match[2], content=match[3].trim();
//print('Preprocessor, match: '+match[0]+'; instruction = '+ instruction + ', content = '+content+'; string='+mutableData.string);
switch (instruction) {
case 'eval':
throw new Exception('instruction "eval" is not supported');
break;
case 'value': case 'val':
String include = defines[content].toString();
int removeAfterLength = 0;
String first2=match.input.length >= match.end +2 ? match.input.substring(match.end,match.end+2) : null;
if(first2 != null && first2=='0 ') removeAfterLength = 2;
else if(first2 != null && ['0;','0,','0)','0.','0+','0-'].contains(first2)) removeAfterLength = 1;
else if(first2 != null && first2=="''") removeAfterLength = 2;
else if(match.input.length >= match.end +5 && match.input.substring(match.end,match.end+5)=='false') removeAfterLength = 5;
else if(match.input.length >= match.end +4 && match.input.substring(match.end,match.end+4)=='true') removeAfterLength = 4;
match.replacePart(match.start, match.end + removeAfterLength, include);
completer.complete();
break;
case 'ifdef': case 'ifndef': case 'if': case 'ifelse':
var include;
if (instruction=='ifdef') {
include = defines.containsKey(content);//!!defines[match2[2]];
} else if (instruction=='ifndef') {
include = !defines.containsKey(content);//!defines[match2[2]];
} else if (instruction=='ifelse') {
include = defines[content] ? 1 : 2;
} else{
Match ifThenMatch = regExpIfThen.firstMatch(content);
if(ifThenMatch != null){
include = defines[ifThenMatch[1]] ? ifThenMatch[2] : '';
match.replacePart(match.start, match.end, include);
completer.complete();
break;
}else if(content.endsWith('=>')){ // if var => : then until the end of the line
content = content.substring(0, content.length-2).trim();
if(defines[content])
match.replacePart(match.start,match.end);
else
match.replacePart(match.start,match.input.indexOf("\n",match.end));
}else{
if(content[0]=='!') include = !defines[content.substring(1).trim()];
else include = defines[content];
}
}
stack.add({ "include": include, "start": match.start, "end": match.end });
completer.complete();
break;
case '/if': case 'endif': case 'else': case 'elif': case 'elseif':
if (stack.length == 0)
throw new Exception("Unexpected #"+instruction+": "+match.input.substring(match.start, Math.min(match.start + errorSourceAhead,match.input.length))+"...");
var before = stack.removeFirst();
var include = match.input.substring(before['end'], match.start);
if (before['include'] == 1 || before['include'] == 2) {
if (include[0]=='(' && include.substring(include.length-1)==')') {
include = include.substring(1,include.length-1);
}
include = include.split('||');
if (include.length != 2) {
return completer.completeError(new Exception('ifelse : '+include.length+' != 2 : '+include.join('||')));
}
include = include[before['include']-1];
} else if (!before['include']) {
include = '';
}
match.replacePart(before['start'], match.end, include);
if (instruction == "else" || instruction == "elif" || instruction == "elseif") {
if (instruction=='else'){
include=!before['include'];
} else if(content[0]=='!') {
include = !defines[content.substring(1).trim()];
} else {
include = defines[content];
}
stack.add({ "include": !before["include"], "start": match.lastIndex, "lastIndex": match.lastIndex });
}
completer.complete();
break;
case 'include': case 'includeOnce':
/*if(content.slice(-1) === '/') content += sysPath.basename(content) + '.js';
else if(content.slice(-3) !== '.js') content += '.js';
if(content.substr(0,1) !== '/') content = dirname + content;
var path = (pathResolver||fs.realpathSync)(content);
if(instruction === 'includeOnce' && includedFiles.indexOf(path) !== -1){
data = data.substring(0, match.index) + '' + data.substring(EXPR.lastIndex + removeAfterLength);
onEnd();
}else{
includedFiles.push(path);
fs.readFile(path,function(err,content){
if(err) return onEnd(err);
module.exports(defines, content, baseDir, includedFiles,function(err,include){
if(err) return onEnd(err);
data = data.substring(0, match.index) + content + data.substring(EXPR.lastIndex + removeAfterLength);
onEnd();
});
});
}*/
completer.complete();
break;
default:
completer.complete();
}
return completer.future;
}).then((_){
completerPreprocessor.complete(mutableData.string);
}, onError: completerPreprocessor.completeError );
return completerPreprocessor.future;
}
}
Static Properties
final int errorSourceAhead #
static final int errorSourceAhead = 50
final RegExp regExpIfThen #
static final RegExp regExpIfThen = new RegExp(r'^(.*) then (.*)$')
final List<String> singleLinesSupportedTypes #
static final List<String> singleLinesSupportedTypes = [ 'js', 'php', 'dart', 'less', 'styl', 'scss' ]
final List<String> types #
static final List<String> types = [ 'js', 'php', 'dart', 'css', 'less', 'styl', 'scss', ]
Static Methods
String indent(String str, String indent) #
static String indent(String str, String indent){
return str.split("\n").map((line) => indent + line).join("\n");
}
Constructors
new Preprocessor(String _type, [Function _pathResolver]) #
Creates a new Object instance.
Object instances have no meaningful state, and are only useful
through their identity. An Object instance is equal to itself
only.
docs inherited from Object
Preprocessor(String this._type, [ Function this._pathResolver ]){
var EXPR_INSTRUCTIONS = '(include(?:Once)?|ifn?def|ifelse|if|\/if|endif|else|el(?:se)?if|eval|value|val|setbasedir)';
var multilineRegExp = new RegExp(r'(^[ \t]*)?\/\*[ ]*#[ ]*' + EXPR_INSTRUCTIONS + r'([^\*]*)[ ]*\*\/', multiLine:true);
var singlelineRegExp = new RegExp(r'(^[ \t]*)(?:\/\/)?#' + EXPR_INSTRUCTIONS + r'(.*)$', multiLine:true);
Set<RegExp> regExps = new Set();
regExps.add(multilineRegExp);
if (singleLinesSupportedTypes.contains(_type)) {
regExps.add(singlelineRegExp);
}
if (this._type == 'js') {
regExps.add(new RegExp(r'''(^[ \t]*)?(include(?:Once)?)\('([^\)]*)'\)'''));
}
_multiRegExp = new MultiRegExp.fromIterable(regExps);
}
Methods
Future<String> process(Map<String, dynamic> defines, String data) #
Future<String> process(Map<String, dynamic> defines, String data) {
assert(data != null);
MutableString mutableData = new MutableString(data);
Completer<String> completerPreprocessor = new Completer();
Queue stack = new Queue(); // Queue is too sophisticated
Future.forEach(mutableData.allMatchesFromMultiRegExp(this._multiRegExp), (MutableStringMatch match){
var completer = new Completer<String>();
String indent = match[1], instruction=match[2], content=match[3].trim();
//print('Preprocessor, match: '+match[0]+'; instruction = '+ instruction + ', content = '+content+'; string='+mutableData.string);
switch (instruction) {
case 'eval':
throw new Exception('instruction "eval" is not supported');
break;
case 'value': case 'val':
String include = defines[content].toString();
int removeAfterLength = 0;
String first2=match.input.length >= match.end +2 ? match.input.substring(match.end,match.end+2) : null;
if(first2 != null && first2=='0 ') removeAfterLength = 2;
else if(first2 != null && ['0;','0,','0)','0.','0+','0-'].contains(first2)) removeAfterLength = 1;
else if(first2 != null && first2=="''") removeAfterLength = 2;
else if(match.input.length >= match.end +5 && match.input.substring(match.end,match.end+5)=='false') removeAfterLength = 5;
else if(match.input.length >= match.end +4 && match.input.substring(match.end,match.end+4)=='true') removeAfterLength = 4;
match.replacePart(match.start, match.end + removeAfterLength, include);
completer.complete();
break;
case 'ifdef': case 'ifndef': case 'if': case 'ifelse':
var include;
if (instruction=='ifdef') {
include = defines.containsKey(content);//!!defines[match2[2]];
} else if (instruction=='ifndef') {
include = !defines.containsKey(content);//!defines[match2[2]];
} else if (instruction=='ifelse') {
include = defines[content] ? 1 : 2;
} else{
Match ifThenMatch = regExpIfThen.firstMatch(content);
if(ifThenMatch != null){
include = defines[ifThenMatch[1]] ? ifThenMatch[2] : '';
match.replacePart(match.start, match.end, include);
completer.complete();
break;
}else if(content.endsWith('=>')){ // if var => : then until the end of the line
content = content.substring(0, content.length-2).trim();
if(defines[content])
match.replacePart(match.start,match.end);
else
match.replacePart(match.start,match.input.indexOf("\n",match.end));
}else{
if(content[0]=='!') include = !defines[content.substring(1).trim()];
else include = defines[content];
}
}
stack.add({ "include": include, "start": match.start, "end": match.end });
completer.complete();
break;
case '/if': case 'endif': case 'else': case 'elif': case 'elseif':
if (stack.length == 0)
throw new Exception("Unexpected #"+instruction+": "+match.input.substring(match.start, Math.min(match.start + errorSourceAhead,match.input.length))+"...");
var before = stack.removeFirst();
var include = match.input.substring(before['end'], match.start);
if (before['include'] == 1 || before['include'] == 2) {
if (include[0]=='(' && include.substring(include.length-1)==')') {
include = include.substring(1,include.length-1);
}
include = include.split('||');
if (include.length != 2) {
return completer.completeError(new Exception('ifelse : '+include.length+' != 2 : '+include.join('||')));
}
include = include[before['include']-1];
} else if (!before['include']) {
include = '';
}
match.replacePart(before['start'], match.end, include);
if (instruction == "else" || instruction == "elif" || instruction == "elseif") {
if (instruction=='else'){
include=!before['include'];
} else if(content[0]=='!') {
include = !defines[content.substring(1).trim()];
} else {
include = defines[content];
}
stack.add({ "include": !before["include"], "start": match.lastIndex, "lastIndex": match.lastIndex });
}
completer.complete();
break;
case 'include': case 'includeOnce':
/*if(content.slice(-1) === '/') content += sysPath.basename(content) + '.js';
else if(content.slice(-3) !== '.js') content += '.js';
if(content.substr(0,1) !== '/') content = dirname + content;
var path = (pathResolver||fs.realpathSync)(content);
if(instruction === 'includeOnce' && includedFiles.indexOf(path) !== -1){
data = data.substring(0, match.index) + '' + data.substring(EXPR.lastIndex + removeAfterLength);
onEnd();
}else{
includedFiles.push(path);
fs.readFile(path,function(err,content){
if(err) return onEnd(err);
module.exports(defines, content, baseDir, includedFiles,function(err,include){
if(err) return onEnd(err);
data = data.substring(0, match.index) + content + data.substring(EXPR.lastIndex + removeAfterLength);
onEnd();
});
});
}*/
completer.complete();
break;
default:
completer.complete();
}
return completer.future;
}).then((_){
completerPreprocessor.complete(mutableData.string);
}, onError: completerPreprocessor.completeError );
return completerPreprocessor.future;
}