processmaker-builder
Version:
The gulp task runner for ProcessMaker building
429 lines (362 loc) • 10.2 kB
JavaScript
var SchemeParser = Editor.Parser = (function() {
// isLparen: char -> boolean
var isLparen = function(ch) {
return ch === '(' || ch === '[' || ch === '{';
};
// isRparen: char -> boolean
var isRparen = function(ch) {
return ch === ')' || ch === ']' || ch === '}';
};
// isMatchingParens: char char -> boolean
var isMatchingParens = function(lparen, rparen) {
return ((lparen === '(' && rparen === ')') ||
(lparen === '[' && rparen === ']') ||
(lparen === '{' && rparen === '}'));
};
// Compute the indentation context enclosing the end of the token
// sequence tokens.
// The context is the token sequence of the enclosing s-expression,
// augmented with column information.
var getIndentationContext = function(tokenStack) {
var EMPTY_CONTEXT = [];
var pendingParens = [], i = 0, j, line, column, context;
var tokens = [];
// Scan for the start of the indentation context, accumulating tokens.
while (! isEmptyPair(tokenStack)) {
i++;
tokens.push(pairFirst(tokenStack));
if (isLparen(pairFirst(tokenStack).type)) {
if (pendingParens.length === 0) {
break;
} else {
if (isMatchingParens(pairFirst(tokenStack).value,
pendingParens[pendingParens.length - 1])) {
pendingParens.pop();
} else {
// Error condition: we see mismatching parens,
// so we exit with no known indentation context.
return EMPTY_CONTEXT;
}
}
} else if (isRparen(pairFirst(tokenStack).type)) {
pendingParens.push(pairFirst(tokenStack).type);
}
tokenStack = pairRest(tokenStack);
}
// If we scanned backward too far, we couldn't find a context. Just
// return the empty context.
if (isEmptyPair(tokenStack)) {
return EMPTY_CONTEXT;
}
// Position tokenStack to the next token beyond.
tokenStack = pairRest(tokenStack);
// We now scan backwards to closest newline to figure out the column
// number:
while (! isEmptyPair(tokenStack)) {
if(pairFirst(tokenStack).type === 'whitespace' &&
pairFirst(tokenStack).value === '\n') {
break;
}
tokens.push(pairFirst(tokenStack));
tokenStack = pairRest(tokenStack);
}
line = 0;
column = 0;
context = [];
// Start generating the context, walking forward.
for (j = tokens.length-1; j >= 0; j--) {
if (j < i) {
context.push({ type: tokens[j].type,
value: tokens[j].value,
line: line,
column: column });
}
if (tokens[j].type === 'whitespace' &&
tokens[j].value === '\n') {
column = 0;
line++;
} else {
column += tokens[j].value.length;
}
}
return context;
};
// calculateIndentationFromContext: indentation-context number -> number
var calculateIndentationFromContext = function(context, currentIndentation) {
if (context.length === 0) {
return 0;
}
if (isBeginLikeContext(context)) {
return beginLikeIndentation(context);
}
if (isDefineLikeContext(context)) {
return defineLikeIndentation(context);
}
if (isLambdaLikeContext(context)) {
return lambdaLikeIndentation(context);
}
return beginLikeIndentation(context, 0);
};
// findContextElement: indentation-context number -> index or -1
var findContextElement = function(context, index) {
var depth = 0;
for(var i = 0; i < context.length; i++) {
if (context[i].type !== 'whitespace' && depth === 1) {
if (index === 0)
return i;
else
index--;
}
if (isLparen(context[i].type)) {
depth++;
}
if (isRparen(context[i].type)) {
depth = Math.max(depth - 1, 0);
}
}
return -1;
};
// contextElement: context -> (arrayof index)
var contextElements = function(context) {
var i = 0, index, results = [];
while ((index = findContextElement(context, i++)) != -1) {
results.push(index);
}
return results;
};
//////////////////////////////////////////////////////////////////////
var BEGIN_LIKE_KEYWORDS = ["case-lambda",
"compound-unit",
"compound-unit/sig",
"cond",
"delay",
"inherit",
"match-lambda",
"match-lambda*",
"override",
"private",
"public",
"sequence",
"unit"];
var isBeginLikeContext = function(context) {
var j = findContextElement(context, 0);
if (j === -1) { return false; }
return (/^begin/.test(context[j].value) ||
isMember(context[j].value, BEGIN_LIKE_KEYWORDS));
};
// Begin: if there's no elements within the begin context,
// the indentation is that of the begin keyword's column + offset.
// Otherwise, find the leading element on the last line.
// Also used for default indentation.
var beginLikeIndentation = function(context, offset) {
if (typeof(offset) === 'undefined') { offset = 1; }
var indices = contextElements(context), j;
if (indices.length === 0) {
return context[0].column + 1;
} else if (indices.length === 1) {
// if we only see the begin keyword, indentation is based
// off the keyword.
return context[indices[0]].column + offset;
} else {
// Otherwise, we scan for the contextElement of the last line
for (j = indices.length -1; j > 1; j--) {
if (context[indices[j]].line !==
context[indices[j-1]].line) {
return context[indices[j]].column;
}
}
return context[indices[j]].column;
}
};
//////////////////////////////////////////////////////////////////////
var DEFINE_LIKE_KEYWORDS = ["local"];
var isDefineLikeContext = function(context) {
var j = findContextElement(context, 0);
if (j === -1) { return false; }
return (/^def/.test(context[j].value) ||
isMember(context[j].value, DEFINE_LIKE_KEYWORDS));
};
var defineLikeIndentation = function(context) {
var i = findContextElement(context, 0);
if (i === -1) { return 0; }
return context[i].column +1;
};
//////////////////////////////////////////////////////////////////////
var LAMBDA_LIKE_KEYWORDS = ["cases",
"instantiate",
"super-instantiate",
"syntax/loc",
"quasisyntax/loc",
"lambda",
"let",
"let*",
"letrec",
"recur",
"lambda/kw",
"letrec-values",
"with-syntax",
"with-continuation-mark",
"module",
"match",
"match-let",
"match-let*",
"match-letrec",
"let/cc",
"let/ec",
"letcc",
"catch",
"let-syntax",
"letrec-syntax",
"fluid-let-syntax",
"letrec-syntaxes+values",
"for",
"for/list",
"for/hash",
"for/hasheq",
"for/and",
"for/or",
"for/lists",
"for/first",
"for/last",
"for/fold",
"for*",
"for*/list",
"for*/hash",
"for*/hasheq",
"for*/and",
"for*/or",
"for*/lists",
"for*/first",
"for*/last",
"for*/fold",
"kernel-syntax-case",
"syntax-case",
"syntax-case*",
"syntax-rules",
"syntax-id-rules",
"let-signature",
"fluid-let",
"let-struct",
"let-macro",
"let-values",
"let*-values",
"case",
"when",
"unless",
"let-enumerate",
"class",
"class*",
"class-asi",
"class-asi*",
"class*/names",
"class100",
"class100*",
"class100-asi",
"class100-asi*",
"class100*/names",
"rec",
"make-object",
"mixin",
"define-some",
"do",
"opt-lambda",
"send*",
"with-method",
"define-record",
"catch",
"shared",
"unit/sig",
"unit/lang",
"with-handlers",
"interface",
"parameterize",
"call-with-input-file",
"call-with-input-file*",
"with-input-from-file",
"with-input-from-port",
"call-with-output-file",
"with-output-to-file",
"with-output-to-port",
"for-all"];
var isLambdaLikeContext = function(context) {
var j = findContextElement(context, 0);
if (j === -1) { return false; }
return (isMember(context[j].value, LAMBDA_LIKE_KEYWORDS));
};
var lambdaLikeIndentation = function(context) {
var i = findContextElement(context, 0);
if (i === -1) { return 0; }
var j = findContextElement(context, 1);
if (j === -1) {
return context[i].column + 4;
} else {
return context[i].column + 1;
}
};
//////////////////////////////////////////////////////////////////////
// Helpers
var isMember = function(x, l) {
for (var i = 0; i < l.length; i++) {
if (x === l[i]) { return true; }
}
return false;
};
//////////////////////////////////////////////////////////////////////
var pair = function(x, y) {
return [x,y];
};
var EMPTY_PAIR = [];
var pairFirst = function(p) { return p[0]; }
var pairRest = function(p) { return p[1]; }
var isEmptyPair = function(p) { return p === EMPTY_PAIR; }
var pairLength = function(p) {
var l = 0;
while (! isEmptyPair(p)) {
p = pairRest(p);
}
return l;
};
//////////////////////////////////////////////////////////////////////
var indentTo = function(tokenStack) {
return function(tokenText, currentIndentation, direction) {
// If we're in the middle of an unclosed token,
// do not change indentation.
if ((! isEmptyPair(tokenStack)) &&
(! isEmptyPair(pairRest(tokenStack))) &&
(pairFirst(pairRest(tokenStack)).isUnclosed)) {
return currentIndentation;
}
var indentationContext =
getIndentationContext(tokenStack);
return calculateIndentationFromContext(indentationContext,
currentIndentation);
};
};
var startParse = function(source) {
source = tokenizeScheme(source);
var tokenStack = EMPTY_PAIR;
var iter = {
next: function() {
var tok = source.next();
tokenStack = pair(tok, tokenStack);
if (tok.type === "whitespace") {
if (tok.value === "\n") {
tok.indentation = indentTo(tokenStack);
}
}
return tok;
},
copy: function() {
var _tokenStack = tokenStack;
var _tokenState = source.state;
return function(_source) {
tokenStack = _tokenStack;
source = tokenizeScheme(_source, _tokenState);
return iter;
};
}
};
return iter;
};
return { make: startParse };
})();