UNPKG

processmaker-builder

Version:

The gulp task runner for ProcessMaker building

429 lines (362 loc) 10.2 kB
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 }; })();