mume-with-litvis
Version:
Fork of mume with added http://litvis.org/
1,482 lines (1,276 loc) • 55.1 kB
JavaScript
/** js sequence diagrams 2.0.1
* https://bramp.github.io/js-sequence-diagrams/
* (c) 2012-2017 Andrew Brampton (bramp.net)
* @license Simplified BSD license.
*/
(function() {
'use strict';
/*global Diagram */
// The following are included by preprocessor */
/** js sequence diagrams
* https://bramp.github.io/js-sequence-diagrams/
* (c) 2012-2017 Andrew Brampton (bramp.net)
* Simplified BSD license.
*/
/*global grammar _ */
function Diagram() {
this.title = undefined;
this.actors = [];
this.signals = [];
}
/*
* Return an existing actor with this alias, or creates a new one with alias and name.
*/
Diagram.prototype.getActor = function(alias, name) {
alias = alias.trim();
var i;
var actors = this.actors;
for (i in actors) {
if (actors[i].alias == alias) {
return actors[i];
}
}
i = actors.push(new Diagram.Actor(alias, (name || alias), actors.length));
return actors[ i - 1 ];
};
/*
* Parses the input as either a alias, or a "name as alias", and returns the corresponding actor.
*/
Diagram.prototype.getActorWithAlias = function(input) {
input = input.trim();
// We are lazy and do some of the parsing in javascript :(. TODO move into the .jison file.
var s = /([\s\S]+) as (\S+)$/im.exec(input);
var alias;
var name;
if (s) {
name = s[1].trim();
alias = s[2].trim();
} else {
name = alias = input;
}
return this.getActor(alias, name);
};
Diagram.prototype.setTitle = function(title) {
this.title = title;
};
Diagram.prototype.addSignal = function(signal) {
this.signals.push(signal);
};
Diagram.Actor = function(alias, name, index) {
this.alias = alias;
this.name = name;
this.index = index;
};
Diagram.Signal = function(actorA, signaltype, actorB, message) {
this.type = 'Signal';
this.actorA = actorA;
this.actorB = actorB;
this.linetype = signaltype & 3;
this.arrowtype = (signaltype >> 2) & 3;
this.message = message;
};
Diagram.Signal.prototype.isSelf = function() {
return this.actorA.index == this.actorB.index;
};
Diagram.Note = function(actor, placement, message) {
this.type = 'Note';
this.actor = actor;
this.placement = placement;
this.message = message;
if (this.hasManyActors() && actor[0] == actor[1]) {
throw new Error('Note should be over two different actors');
}
};
Diagram.Note.prototype.hasManyActors = function() {
return _.isArray(this.actor);
};
Diagram.unescape = function(s) {
// Turn "\\n" into "\n"
return s.trim().replace(/^"(.*)"$/m, '$1').replace(/\\n/gm, '\n');
};
Diagram.LINETYPE = {
SOLID: 0,
DOTTED: 1
};
Diagram.ARROWTYPE = {
FILLED: 0,
OPEN: 1
};
Diagram.PLACEMENT = {
LEFTOF: 0,
RIGHTOF: 1,
OVER: 2
};
// Some older browsers don't have getPrototypeOf, thus we polyfill it
// https://github.com/bramp/js-sequence-diagrams/issues/57
// https://github.com/zaach/jison/issues/194
// Taken from http://ejohn.org/blog/objectgetprototypeof/
if (typeof Object.getPrototypeOf !== 'function') {
/* jshint -W103 */
if (typeof 'test'.__proto__ === 'object') {
Object.getPrototypeOf = function(object) {
return object.__proto__;
};
} else {
Object.getPrototypeOf = function(object) {
// May break if the constructor has been tampered with
return object.constructor.prototype;
};
}
/* jshint +W103 */
}
/** The following is included by preprocessor */
/* parser generated by jison 0.4.15 */
/*
Returns a Parser object of the following structure:
Parser: {
yy: {}
}
Parser.prototype: {
yy: {},
trace: function(),
symbols_: {associative list: name ==> number},
terminals_: {associative list: number ==> name},
productions_: [...],
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
table: [...],
defaultActions: {...},
parseError: function(str, hash),
parse: function(input),
lexer: {
EOF: 1,
parseError: function(str, hash),
setInput: function(input),
input: function(),
unput: function(str),
more: function(),
less: function(n),
pastInput: function(),
upcomingInput: function(),
showPosition: function(),
test_match: function(regex_match_array, rule_index),
next: function(),
lex: function(),
begin: function(condition),
popState: function(),
_currentRules: function(),
topState: function(),
pushState: function(condition),
options: {
ranges: boolean (optional: true ==> token location info will include a .range[] member)
flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
},
performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
rules: [...],
conditions: {associative list: name ==> set},
}
}
token location info (@$, _$, etc.): {
first_line: n,
last_line: n,
first_column: n,
last_column: n,
range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
}
the parseError function receives a 'hash' object with these members for lexer and parser errors: {
text: (matched text)
token: (the produced terminal token, if any)
line: (yylineno)
}
while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
loc: (yylloc)
expected: (string describing the set of expected tokens)
recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
}
*/
var parser = function() {
function Parser() {
this.yy = {};
}
var o = function(k, v, o, l) {
for (o = o || {}, l = k.length; l--; o[k[l]] = v) ;
return o;
}, $V0 = [ 5, 8, 9, 13, 15, 24 ], $V1 = [ 1, 13 ], $V2 = [ 1, 17 ], $V3 = [ 24, 29, 30 ], parser = {
trace: function() {},
yy: {},
symbols_: {
error: 2,
start: 3,
document: 4,
EOF: 5,
line: 6,
statement: 7,
NL: 8,
participant: 9,
actor_alias: 10,
signal: 11,
note_statement: 12,
title: 13,
message: 14,
note: 15,
placement: 16,
actor: 17,
over: 18,
actor_pair: 19,
",": 20,
left_of: 21,
right_of: 22,
signaltype: 23,
ACTOR: 24,
linetype: 25,
arrowtype: 26,
LINE: 27,
DOTLINE: 28,
ARROW: 29,
OPENARROW: 30,
MESSAGE: 31,
$accept: 0,
$end: 1
},
terminals_: {
2: "error",
5: "EOF",
8: "NL",
9: "participant",
13: "title",
15: "note",
18: "over",
20: ",",
21: "left_of",
22: "right_of",
24: "ACTOR",
27: "LINE",
28: "DOTLINE",
29: "ARROW",
30: "OPENARROW",
31: "MESSAGE"
},
productions_: [ 0, [ 3, 2 ], [ 4, 0 ], [ 4, 2 ], [ 6, 1 ], [ 6, 1 ], [ 7, 2 ], [ 7, 1 ], [ 7, 1 ], [ 7, 2 ], [ 12, 4 ], [ 12, 4 ], [ 19, 1 ], [ 19, 3 ], [ 16, 1 ], [ 16, 1 ], [ 11, 4 ], [ 17, 1 ], [ 10, 1 ], [ 23, 2 ], [ 23, 1 ], [ 25, 1 ], [ 25, 1 ], [ 26, 1 ], [ 26, 1 ], [ 14, 1 ] ],
performAction: function(yytext, yyleng, yylineno, yy, yystate, $$, _$) {
/* this == yyval */
var $0 = $$.length - 1;
switch (yystate) {
case 1:
return yy.parser.yy;
case 4:
break;
case 6:
$$[$0];
break;
case 7:
case 8:
yy.parser.yy.addSignal($$[$0]);
break;
case 9:
yy.parser.yy.setTitle($$[$0]);
break;
case 10:
this.$ = new Diagram.Note($$[$0 - 1], $$[$0 - 2], $$[$0]);
break;
case 11:
this.$ = new Diagram.Note($$[$0 - 1], Diagram.PLACEMENT.OVER, $$[$0]);
break;
case 12:
case 20:
this.$ = $$[$0];
break;
case 13:
this.$ = [ $$[$0 - 2], $$[$0] ];
break;
case 14:
this.$ = Diagram.PLACEMENT.LEFTOF;
break;
case 15:
this.$ = Diagram.PLACEMENT.RIGHTOF;
break;
case 16:
this.$ = new Diagram.Signal($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0]);
break;
case 17:
this.$ = yy.parser.yy.getActor(Diagram.unescape($$[$0]));
break;
case 18:
this.$ = yy.parser.yy.getActorWithAlias(Diagram.unescape($$[$0]));
break;
case 19:
this.$ = $$[$0 - 1] | $$[$0] << 2;
break;
case 21:
this.$ = Diagram.LINETYPE.SOLID;
break;
case 22:
this.$ = Diagram.LINETYPE.DOTTED;
break;
case 23:
this.$ = Diagram.ARROWTYPE.FILLED;
break;
case 24:
this.$ = Diagram.ARROWTYPE.OPEN;
break;
case 25:
this.$ = Diagram.unescape($$[$0].substring(1));
}
},
table: [ o($V0, [ 2, 2 ], {
3: 1,
4: 2
}), {
1: [ 3 ]
}, {
5: [ 1, 3 ],
6: 4,
7: 5,
8: [ 1, 6 ],
9: [ 1, 7 ],
11: 8,
12: 9,
13: [ 1, 10 ],
15: [ 1, 12 ],
17: 11,
24: $V1
}, {
1: [ 2, 1 ]
}, o($V0, [ 2, 3 ]), o($V0, [ 2, 4 ]), o($V0, [ 2, 5 ]), {
10: 14,
24: [ 1, 15 ]
}, o($V0, [ 2, 7 ]), o($V0, [ 2, 8 ]), {
14: 16,
31: $V2
}, {
23: 18,
25: 19,
27: [ 1, 20 ],
28: [ 1, 21 ]
}, {
16: 22,
18: [ 1, 23 ],
21: [ 1, 24 ],
22: [ 1, 25 ]
}, o([ 20, 27, 28, 31 ], [ 2, 17 ]), o($V0, [ 2, 6 ]), o($V0, [ 2, 18 ]), o($V0, [ 2, 9 ]), o($V0, [ 2, 25 ]), {
17: 26,
24: $V1
}, {
24: [ 2, 20 ],
26: 27,
29: [ 1, 28 ],
30: [ 1, 29 ]
}, o($V3, [ 2, 21 ]), o($V3, [ 2, 22 ]), {
17: 30,
24: $V1
}, {
17: 32,
19: 31,
24: $V1
}, {
24: [ 2, 14 ]
}, {
24: [ 2, 15 ]
}, {
14: 33,
31: $V2
}, {
24: [ 2, 19 ]
}, {
24: [ 2, 23 ]
}, {
24: [ 2, 24 ]
}, {
14: 34,
31: $V2
}, {
14: 35,
31: $V2
}, {
20: [ 1, 36 ],
31: [ 2, 12 ]
}, o($V0, [ 2, 16 ]), o($V0, [ 2, 10 ]), o($V0, [ 2, 11 ]), {
17: 37,
24: $V1
}, {
31: [ 2, 13 ]
} ],
defaultActions: {
3: [ 2, 1 ],
24: [ 2, 14 ],
25: [ 2, 15 ],
27: [ 2, 19 ],
28: [ 2, 23 ],
29: [ 2, 24 ],
37: [ 2, 13 ]
},
parseError: function(str, hash) {
if (!hash.recoverable) throw new Error(str);
this.trace(str);
},
parse: function(input) {
function lex() {
var token;
return token = lexer.lex() || EOF, "number" != typeof token && (token = self.symbols_[token] || token),
token;
}
var self = this, stack = [ 0 ], vstack = [ null ], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1, args = lstack.slice.call(arguments, 1), lexer = Object.create(this.lexer), sharedState = {
yy: {}
};
for (var k in this.yy) Object.prototype.hasOwnProperty.call(this.yy, k) && (sharedState.yy[k] = this.yy[k]);
lexer.setInput(input, sharedState.yy), sharedState.yy.lexer = lexer, sharedState.yy.parser = this,
"undefined" == typeof lexer.yylloc && (lexer.yylloc = {});
var yyloc = lexer.yylloc;
lstack.push(yyloc);
var ranges = lexer.options && lexer.options.ranges;
"function" == typeof sharedState.yy.parseError ? this.parseError = sharedState.yy.parseError : this.parseError = Object.getPrototypeOf(this).parseError;
for (var symbol, preErrorSymbol, state, action, r, p, len, newState, expected, yyval = {}; ;) {
if (state = stack[stack.length - 1], this.defaultActions[state] ? action = this.defaultActions[state] : (null !== symbol && "undefined" != typeof symbol || (symbol = lex()),
action = table[state] && table[state][symbol]), "undefined" == typeof action || !action.length || !action[0]) {
var errStr = "";
expected = [];
for (p in table[state]) this.terminals_[p] && p > TERROR && expected.push("'" + this.terminals_[p] + "'");
errStr = lexer.showPosition ? "Parse error on line " + (yylineno + 1) + ":\n" + lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'" : "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == EOF ? "end of input" : "'" + (this.terminals_[symbol] || symbol) + "'"),
this.parseError(errStr, {
text: lexer.match,
token: this.terminals_[symbol] || symbol,
line: lexer.yylineno,
loc: yyloc,
expected: expected
});
}
if (action[0] instanceof Array && action.length > 1) throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
switch (action[0]) {
case 1:
stack.push(symbol), vstack.push(lexer.yytext), lstack.push(lexer.yylloc), stack.push(action[1]),
symbol = null, preErrorSymbol ? (symbol = preErrorSymbol, preErrorSymbol = null) : (yyleng = lexer.yyleng,
yytext = lexer.yytext, yylineno = lexer.yylineno, yyloc = lexer.yylloc, recovering > 0 && recovering--);
break;
case 2:
if (len = this.productions_[action[1]][1], yyval.$ = vstack[vstack.length - len],
yyval._$ = {
first_line: lstack[lstack.length - (len || 1)].first_line,
last_line: lstack[lstack.length - 1].last_line,
first_column: lstack[lstack.length - (len || 1)].first_column,
last_column: lstack[lstack.length - 1].last_column
}, ranges && (yyval._$.range = [ lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1] ]),
r = this.performAction.apply(yyval, [ yytext, yyleng, yylineno, sharedState.yy, action[1], vstack, lstack ].concat(args)),
"undefined" != typeof r) return r;
len && (stack = stack.slice(0, -1 * len * 2), vstack = vstack.slice(0, -1 * len),
lstack = lstack.slice(0, -1 * len)), stack.push(this.productions_[action[1]][0]),
vstack.push(yyval.$), lstack.push(yyval._$), newState = table[stack[stack.length - 2]][stack[stack.length - 1]],
stack.push(newState);
break;
case 3:
return !0;
}
}
return !0;
}
}, lexer = function() {
var lexer = {
EOF: 1,
parseError: function(str, hash) {
if (!this.yy.parser) throw new Error(str);
this.yy.parser.parseError(str, hash);
},
// resets the lexer, sets new input
setInput: function(input, yy) {
return this.yy = yy || this.yy || {}, this._input = input, this._more = this._backtrack = this.done = !1,
this.yylineno = this.yyleng = 0, this.yytext = this.matched = this.match = "", this.conditionStack = [ "INITIAL" ],
this.yylloc = {
first_line: 1,
first_column: 0,
last_line: 1,
last_column: 0
}, this.options.ranges && (this.yylloc.range = [ 0, 0 ]), this.offset = 0, this;
},
// consumes and returns one char from the input
input: function() {
var ch = this._input[0];
this.yytext += ch, this.yyleng++, this.offset++, this.match += ch, this.matched += ch;
var lines = ch.match(/(?:\r\n?|\n).*/g);
return lines ? (this.yylineno++, this.yylloc.last_line++) : this.yylloc.last_column++,
this.options.ranges && this.yylloc.range[1]++, this._input = this._input.slice(1),
ch;
},
// unshifts one char (or a string) into the input
unput: function(ch) {
var len = ch.length, lines = ch.split(/(?:\r\n?|\n)/g);
this._input = ch + this._input, this.yytext = this.yytext.substr(0, this.yytext.length - len),
//this.yyleng -= len;
this.offset -= len;
var oldLines = this.match.split(/(?:\r\n?|\n)/g);
this.match = this.match.substr(0, this.match.length - 1), this.matched = this.matched.substr(0, this.matched.length - 1),
lines.length - 1 && (this.yylineno -= lines.length - 1);
var r = this.yylloc.range;
return this.yylloc = {
first_line: this.yylloc.first_line,
last_line: this.yylineno + 1,
first_column: this.yylloc.first_column,
last_column: lines ? (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length : this.yylloc.first_column - len
}, this.options.ranges && (this.yylloc.range = [ r[0], r[0] + this.yyleng - len ]),
this.yyleng = this.yytext.length, this;
},
// When called from action, caches matched text and appends it on next action
more: function() {
return this._more = !0, this;
},
// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
reject: function() {
return this.options.backtrack_lexer ? (this._backtrack = !0, this) : this.parseError("Lexical error on line " + (this.yylineno + 1) + ". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n" + this.showPosition(), {
text: "",
token: null,
line: this.yylineno
});
},
// retain first n characters of the match
less: function(n) {
this.unput(this.match.slice(n));
},
// displays already matched input, i.e. for error messages
pastInput: function() {
var past = this.matched.substr(0, this.matched.length - this.match.length);
return (past.length > 20 ? "..." : "") + past.substr(-20).replace(/\n/g, "");
},
// displays upcoming input, i.e. for error messages
upcomingInput: function() {
var next = this.match;
return next.length < 20 && (next += this._input.substr(0, 20 - next.length)), (next.substr(0, 20) + (next.length > 20 ? "..." : "")).replace(/\n/g, "");
},
// displays the character position where the lexing error occurred, i.e. for error messages
showPosition: function() {
var pre = this.pastInput(), c = new Array(pre.length + 1).join("-");
return pre + this.upcomingInput() + "\n" + c + "^";
},
// test the lexed token: return FALSE when not a match, otherwise return token
test_match: function(match, indexed_rule) {
var token, lines, backup;
if (this.options.backtrack_lexer && (// save context
backup = {
yylineno: this.yylineno,
yylloc: {
first_line: this.yylloc.first_line,
last_line: this.last_line,
first_column: this.yylloc.first_column,
last_column: this.yylloc.last_column
},
yytext: this.yytext,
match: this.match,
matches: this.matches,
matched: this.matched,
yyleng: this.yyleng,
offset: this.offset,
_more: this._more,
_input: this._input,
yy: this.yy,
conditionStack: this.conditionStack.slice(0),
done: this.done
}, this.options.ranges && (backup.yylloc.range = this.yylloc.range.slice(0))), lines = match[0].match(/(?:\r\n?|\n).*/g),
lines && (this.yylineno += lines.length), this.yylloc = {
first_line: this.yylloc.last_line,
last_line: this.yylineno + 1,
first_column: this.yylloc.last_column,
last_column: lines ? lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length
}, this.yytext += match[0], this.match += match[0], this.matches = match, this.yyleng = this.yytext.length,
this.options.ranges && (this.yylloc.range = [ this.offset, this.offset += this.yyleng ]),
this._more = !1, this._backtrack = !1, this._input = this._input.slice(match[0].length),
this.matched += match[0], token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]),
this.done && this._input && (this.done = !1), token) return token;
if (this._backtrack) {
// recover context
for (var k in backup) this[k] = backup[k];
return !1;
}
return !1;
},
// return next match in input
next: function() {
if (this.done) return this.EOF;
this._input || (this.done = !0);
var token, match, tempMatch, index;
this._more || (this.yytext = "", this.match = "");
for (var rules = this._currentRules(), i = 0; i < rules.length; i++) if (tempMatch = this._input.match(this.rules[rules[i]]),
tempMatch && (!match || tempMatch[0].length > match[0].length)) {
if (match = tempMatch, index = i, this.options.backtrack_lexer) {
if (token = this.test_match(tempMatch, rules[i]), token !== !1) return token;
if (this._backtrack) {
match = !1;
continue;
}
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
return !1;
}
if (!this.options.flex) break;
}
return match ? (token = this.test_match(match, rules[index]), token !== !1 && token) : "" === this._input ? this.EOF : this.parseError("Lexical error on line " + (this.yylineno + 1) + ". Unrecognized text.\n" + this.showPosition(), {
text: "",
token: null,
line: this.yylineno
});
},
// return next match that has a token
lex: function() {
var r = this.next();
return r ? r : this.lex();
},
// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
begin: function(condition) {
this.conditionStack.push(condition);
},
// pop the previously active lexer condition state off the condition stack
popState: function() {
var n = this.conditionStack.length - 1;
return n > 0 ? this.conditionStack.pop() : this.conditionStack[0];
},
// produce the lexer rule set which is active for the currently active lexer condition state
_currentRules: function() {
return this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1] ? this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules : this.conditions.INITIAL.rules;
},
// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
topState: function(n) {
return n = this.conditionStack.length - 1 - Math.abs(n || 0), n >= 0 ? this.conditionStack[n] : "INITIAL";
},
// alias for begin(condition)
pushState: function(condition) {
this.begin(condition);
},
// return the number of states currently on the stack
stateStackSize: function() {
return this.conditionStack.length;
},
options: {
"case-insensitive": !0
},
performAction: function(yy, yy_, $avoiding_name_collisions, YY_START) {
switch ($avoiding_name_collisions) {
case 0:
return 8;
case 1:
/* skip whitespace */
break;
case 2:
/* skip comments */
break;
case 3:
return 9;
case 4:
return 21;
case 5:
return 22;
case 6:
return 18;
case 7:
return 15;
case 8:
return 13;
case 9:
return 20;
case 10:
return 24;
case 11:
return 24;
case 12:
return 28;
case 13:
return 27;
case 14:
return 30;
case 15:
return 29;
case 16:
return 31;
case 17:
return 5;
case 18:
return "INVALID";
}
},
rules: [ /^(?:[\r\n]+)/i, /^(?:\s+)/i, /^(?:#[^\r\n]*)/i, /^(?:participant\b)/i, /^(?:left of\b)/i, /^(?:right of\b)/i, /^(?:over\b)/i, /^(?:note\b)/i, /^(?:title\b)/i, /^(?:,)/i, /^(?:[^\->:,\r\n"]+)/i, /^(?:"[^"]+")/i, /^(?:--)/i, /^(?:-)/i, /^(?:>>)/i, /^(?:>)/i, /^(?:[^\r\n]+)/i, /^(?:$)/i, /^(?:.)/i ],
conditions: {
INITIAL: {
rules: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 ],
inclusive: !0
}
}
};
return lexer;
}();
return parser.lexer = lexer, Parser.prototype = parser, parser.Parser = Parser,
new Parser();
}();
"undefined" != typeof require && "undefined" != typeof exports && (exports.parser = parser,
exports.Parser = parser.Parser, exports.parse = function() {
return parser.parse.apply(parser, arguments);
}, exports.main = function(args) {
args[1] || (console.log("Usage: " + args[0] + " FILE"), process.exit(1));
var source = require("fs").readFileSync(require("path").normalize(args[1]), "utf8");
return exports.parser.parse(source);
}, "undefined" != typeof module && require.main === module && exports.main(process.argv.slice(1)));
/**
* jison doesn't have a good exception, so we make one.
* This is brittle as it depends on jison internals
*/
function ParseError(message, hash) {
_.extend(this, hash);
this.name = 'ParseError';
this.message = (message || '');
}
ParseError.prototype = new Error();
Diagram.ParseError = ParseError;
Diagram.parse = function(input) {
// TODO jison v0.4.17 changed their API slightly, so parser is no longer defined:
// Create the object to track state and deal with errors
parser.yy = new Diagram();
parser.yy.parseError = function(message, hash) {
throw new ParseError(message, hash);
};
// Parse
var diagram = parser.parse(input);
// Then clean up the parseError key that a user won't care about
delete diagram.parseError;
return diagram;
};
/** js sequence diagrams
* https://bramp.github.io/js-sequence-diagrams/
* (c) 2012-2017 Andrew Brampton (bramp.net)
* Simplified BSD license.
*/
/*global Diagram, _ */
// Following the CSS convention
// Margin is the gap outside the box
// Padding is the gap inside the box
// Each object has x/y/width/height properties
// The x/y should be top left corner
// width/height is with both margin and padding
// TODO
// Image width is wrong, when there is a note in the right hand col
// Title box could look better
// Note box could look better
var DIAGRAM_MARGIN = 10;
var ACTOR_MARGIN = 10; // Margin around a actor
var ACTOR_PADDING = 10; // Padding inside a actor
var SIGNAL_MARGIN = 5; // Margin around a signal
var SIGNAL_PADDING = 5; // Padding inside a signal
var NOTE_MARGIN = 10; // Margin around a note
var NOTE_PADDING = 5; // Padding inside a note
var NOTE_OVERLAP = 15; // Overlap when using a "note over A,B"
var TITLE_MARGIN = 0;
var TITLE_PADDING = 5;
var SELF_SIGNAL_WIDTH = 20; // How far out a self signal goes
var PLACEMENT = Diagram.PLACEMENT;
var LINETYPE = Diagram.LINETYPE;
var ARROWTYPE = Diagram.ARROWTYPE;
var ALIGN_LEFT = 0;
var ALIGN_CENTER = 1;
function AssertException(message) { this.message = message; }
AssertException.prototype.toString = function() {
return 'AssertException: ' + this.message;
};
function assert(exp, message) {
if (!exp) {
throw new AssertException(message);
}
}
if (!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '');
};
}
Diagram.themes = {};
function registerTheme(name, theme) {
Diagram.themes[name] = theme;
}
/******************
* Drawing extras
******************/
function getCenterX(box) {
return box.x + box.width / 2;
}
function getCenterY(box) {
return box.y + box.height / 2;
}
/******************
* SVG Path extras
******************/
function clamp(x, min, max) {
if (x < min) {
return min;
}
if (x > max) {
return max;
}
return x;
}
function wobble(x1, y1, x2, y2) {
assert(_.all([x1,x2,y1,y2], _.isFinite), 'x1,x2,y1,y2 must be numeric');
// Wobble no more than 1/25 of the line length
var factor = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) / 25;
// Distance along line where the control points are
// Clamp between 20% and 80% so any arrow heads aren't angled too much
var r1 = clamp(Math.random(), 0.2, 0.8);
var r2 = clamp(Math.random(), 0.2, 0.8);
var xfactor = Math.random() > 0.5 ? factor : -factor;
var yfactor = Math.random() > 0.5 ? factor : -factor;
var p1 = {
x: (x2 - x1) * r1 + x1 + xfactor,
y: (y2 - y1) * r1 + y1 + yfactor
};
var p2 = {
x: (x2 - x1) * r2 + x1 - xfactor,
y: (y2 - y1) * r2 + y1 - yfactor
};
return 'C' + p1.x.toFixed(1) + ',' + p1.y.toFixed(1) + // start control point
' ' + p2.x.toFixed(1) + ',' + p2.y.toFixed(1) + // end control point
' ' + x2.toFixed(1) + ',' + y2.toFixed(1); // end point
}
/**
* Draws a wobbly (hand drawn) rect
*/
function handRect(x, y, w, h) {
assert(_.all([x, y, w, h], _.isFinite), 'x, y, w, h must be numeric');
return 'M' + x + ',' + y +
wobble(x, y, x + w, y) +
wobble(x + w, y, x + w, y + h) +
wobble(x + w, y + h, x, y + h) +
wobble(x, y + h, x, y);
}
/**
* Draws a wobbly (hand drawn) line
*/
function handLine(x1, y1, x2, y2) {
assert(_.all([x1,x2,y1,y2], _.isFinite), 'x1,x2,y1,y2 must be numeric');
return 'M' + x1.toFixed(1) + ',' + y1.toFixed(1) + wobble(x1, y1, x2, y2);
}
/******************
* BaseTheme
******************/
var BaseTheme = function(diagram, options) {
this.init(diagram, options);
};
_.extend(BaseTheme.prototype, {
// Init called while creating the Theme
init: function(diagram, options) {
this.diagram = diagram;
this.actorsHeight_ = 0;
this.signalsHeight_ = 0;
this.title_ = undefined; // hack - This should be somewhere better
},
setupPaper: function(container) {},
draw: function(container) {
this.setupPaper(container);
this.layout();
var titleHeight = this.title_ ? this.title_.height : 0;
var y = DIAGRAM_MARGIN + titleHeight;
this.drawTitle();
this.drawActors(y);
this.drawSignals(y + this.actorsHeight_);
},
layout: function() {
// Local copies
var diagram = this.diagram;
var font = this.font_;
var actors = diagram.actors;
var signals = diagram.signals;
diagram.width = 0; // min width
diagram.height = 0; // min height
// Setup some layout stuff
if (diagram.title) {
var title = this.title_ = {};
var bb = this.textBBox(diagram.title, font);
title.textBB = bb;
title.message = diagram.title;
title.width = bb.width + (TITLE_PADDING + TITLE_MARGIN) * 2;
title.height = bb.height + (TITLE_PADDING + TITLE_MARGIN) * 2;
title.x = DIAGRAM_MARGIN;
title.y = DIAGRAM_MARGIN;
diagram.width += title.width;
diagram.height += title.height;
}
_.each(actors, function(a) {
var bb = this.textBBox(a.name, font);
a.textBB = bb;
a.x = 0; a.y = 0;
a.width = bb.width + (ACTOR_PADDING + ACTOR_MARGIN) * 2;
a.height = bb.height + (ACTOR_PADDING + ACTOR_MARGIN) * 2;
a.distances = [];
a.paddingRight = 0;
this.actorsHeight_ = Math.max(a.height, this.actorsHeight_);
}, this);
function actorEnsureDistance(a, b, d) {
assert(a < b, 'a must be less than or equal to b');
if (a < 0) {
// Ensure b has left margin
b = actors[b];
b.x = Math.max(d - b.width / 2, b.x);
} else if (b >= actors.length) {
// Ensure a has right margin
a = actors[a];
a.paddingRight = Math.max(d, a.paddingRight);
} else {
a = actors[a];
a.distances[b] = Math.max(d, a.distances[b] ? a.distances[b] : 0);
}
}
_.each(signals, function(s) {
// Indexes of the left and right actors involved
var a;
var b;
var bb = this.textBBox(s.message, font);
//var bb = t.attr("text", s.message).getBBox();
s.textBB = bb;
s.width = bb.width;
s.height = bb.height;
var extraWidth = 0;
if (s.type == 'Signal') {
s.width += (SIGNAL_MARGIN + SIGNAL_PADDING) * 2;
s.height += (SIGNAL_MARGIN + SIGNAL_PADDING) * 2;
if (s.isSelf()) {
// TODO Self signals need a min height
a = s.actorA.index;
b = a + 1;
s.width += SELF_SIGNAL_WIDTH;
} else {
a = Math.min(s.actorA.index, s.actorB.index);
b = Math.max(s.actorA.index, s.actorB.index);
}
} else if (s.type == 'Note') {
s.width += (NOTE_MARGIN + NOTE_PADDING) * 2;
s.height += (NOTE_MARGIN + NOTE_PADDING) * 2;
// HACK lets include the actor's padding
extraWidth = 2 * ACTOR_MARGIN;
if (s.placement == PLACEMENT.LEFTOF) {
b = s.actor.index;
a = b - 1;
} else if (s.placement == PLACEMENT.RIGHTOF) {
a = s.actor.index;
b = a + 1;
} else if (s.placement == PLACEMENT.OVER && s.hasManyActors()) {
// Over multiple actors
a = Math.min(s.actor[0].index, s.actor[1].index);
b = Math.max(s.actor[0].index, s.actor[1].index);
// We don't need our padding, and we want to overlap
extraWidth = -(NOTE_PADDING * 2 + NOTE_OVERLAP * 2);
} else if (s.placement == PLACEMENT.OVER) {
// Over single actor
a = s.actor.index;
actorEnsureDistance(a - 1, a, s.width / 2);
actorEnsureDistance(a, a + 1, s.width / 2);
this.signalsHeight_ += s.height;
return; // Bail out early
}
} else {
throw new Error('Unhandled signal type:' + s.type);
}
actorEnsureDistance(a, b, s.width + extraWidth);
this.signalsHeight_ += s.height;
}, this);
// Re-jig the positions
var actorsX = 0;
_.each(actors, function(a) {
a.x = Math.max(actorsX, a.x);
// TODO This only works if we loop in sequence, 0, 1, 2, etc
_.each(a.distances, function(distance, b) {
// lodash (and possibly others) do not like sparse arrays
// so sometimes they return undefined
if (typeof distance == 'undefined') {
return;
}
b = actors[b];
distance = Math.max(distance, a.width / 2, b.width / 2);
b.x = Math.max(b.x, a.x + a.width / 2 + distance - b.width / 2);
});
actorsX = a.x + a.width + a.paddingRight;
}, this);
diagram.width = Math.max(actorsX, diagram.width);
// TODO Refactor a little
diagram.width += 2 * DIAGRAM_MARGIN;
diagram.height += 2 * DIAGRAM_MARGIN + 2 * this.actorsHeight_ + this.signalsHeight_;
return this;
},
// TODO Instead of one textBBox function, create a function for each element type, e.g
// layout_title, layout_actor, etc that returns it's bounding box
textBBox: function(text, font) {},
drawTitle: function() {
var title = this.title_;
if (title) {
this.drawTextBox(title, title.message, TITLE_MARGIN, TITLE_PADDING, this.font_, ALIGN_LEFT);
}
},
drawActors: function(offsetY) {
var y = offsetY;
_.each(this.diagram.actors, function(a) {
// Top box
this.drawActor(a, y, this.actorsHeight_);
// Bottom box
this.drawActor(a, y + this.actorsHeight_ + this.signalsHeight_, this.actorsHeight_);
// Veritical line
var aX = getCenterX(a);
this.drawLine(
aX, y + this.actorsHeight_ - ACTOR_MARGIN,
aX, y + this.actorsHeight_ + ACTOR_MARGIN + this.signalsHeight_);
}, this);
},
drawActor: function(actor, offsetY, height) {
actor.y = offsetY;
actor.height = height;
this.drawTextBox(actor, actor.name, ACTOR_MARGIN, ACTOR_PADDING, this.font_, ALIGN_CENTER);
},
drawSignals: function(offsetY) {
var y = offsetY;
_.each(this.diagram.signals, function(s) {
// TODO Add debug mode, that draws padding/margin box
if (s.type == 'Signal') {
if (s.isSelf()) {
this.drawSelfSignal(s, y);
} else {
this.drawSignal(s, y);
}
} else if (s.type == 'Note') {
this.drawNote(s, y);
}
y += s.height;
}, this);
},
drawSelfSignal: function(signal, offsetY) {
assert(signal.isSelf(), 'signal must be a self signal');
var textBB = signal.textBB;
var aX = getCenterX(signal.actorA);
var x = aX + SELF_SIGNAL_WIDTH + SIGNAL_PADDING;
var y = offsetY + SIGNAL_PADDING + signal.height / 2 + textBB.y;
this.drawText(x, y, signal.message, this.font_, ALIGN_LEFT);
var y1 = offsetY + SIGNAL_MARGIN + SIGNAL_PADDING;
var y2 = y1 + signal.height - 2 * SIGNAL_MARGIN - SIGNAL_PADDING;
// Draw three lines, the last one with a arrow
this.drawLine(aX, y1, aX + SELF_SIGNAL_WIDTH, y1, signal.linetype);
this.drawLine(aX + SELF_SIGNAL_WIDTH, y1, aX + SELF_SIGNAL_WIDTH, y2, signal.linetype);
this.drawLine(aX + SELF_SIGNAL_WIDTH, y2, aX, y2, signal.linetype, signal.arrowtype);
},
drawSignal: function(signal, offsetY) {
var aX = getCenterX(signal.actorA);
var bX = getCenterX(signal.actorB);
// Mid point between actors
var x = (bX - aX) / 2 + aX;
var y = offsetY + SIGNAL_MARGIN + 2 * SIGNAL_PADDING;
// Draw the text in the middle of the signal
this.drawText(x, y, signal.message, this.font_, ALIGN_CENTER);
// Draw the line along the bottom of the signal
y = offsetY + signal.height - SIGNAL_MARGIN - SIGNAL_PADDING;
this.drawLine(aX, y, bX, y, signal.linetype, signal.arrowtype);
},
drawNote: function(note, offsetY) {
note.y = offsetY;
var actorA = note.hasManyActors() ? note.actor[0] : note.actor;
var aX = getCenterX(actorA);
switch (note.placement) {
case PLACEMENT.RIGHTOF:
note.x = aX + ACTOR_MARGIN;
break;
case PLACEMENT.LEFTOF:
note.x = aX - ACTOR_MARGIN - note.width;
break;
case PLACEMENT.OVER:
if (note.hasManyActors()) {
var bX = getCenterX(note.actor[1]);
var overlap = NOTE_OVERLAP + NOTE_PADDING;
note.x = Math.min(aX, bX) - overlap;
note.width = (Math.max(aX, bX) + overlap) - note.x;
} else {
note.x = aX - note.width / 2;
}
break;
default:
throw new Error('Unhandled note placement: ' + note.placement);
}
return this.drawTextBox(note, note.message, NOTE_MARGIN, NOTE_PADDING, this.font_, ALIGN_LEFT);
},
/**
* Draw text surrounded by a box
*/
drawTextBox: function(box, text, margin, padding, font, align) {
var x = box.x + margin;
var y = box.y + margin;
var w = box.width - 2 * margin;
var h = box.height - 2 * margin;
// Draw inner box
this.drawRect(x, y, w, h);
// Draw text (in the center)
if (align == ALIGN_CENTER) {
x = getCenterX(box);
y = getCenterY(box);
} else {
x += padding;
y += padding;
}
return this.drawText(x, y, text, font, align);
}
});
/** js sequence diagrams
* https://bramp.github.io/js-sequence-diagrams/
* (c) 2012-2017 Andrew Brampton (bramp.net)
* Simplified BSD license.
*/
/*global Diagram, Snap, WebFont _ */
// TODO Move defintion of font onto the <svg>, so it can easily be override at each level
if (typeof Snap != 'undefined') {
var xmlns = 'http://www.w3.org/2000/svg';
var LINE = {
'stroke': '#000000',
'stroke-width': 2, // BUG TODO This gets set as a style, not as a attribute. Look at eve.on("snap.util.attr"...
'fill': 'none'
};
var RECT = {
'stroke': '#000000',
'stroke-width': 2,
'fill': '#fff'
};
var LOADED_FONTS = {};
/******************
* SnapTheme
******************/
var SnapTheme = function(diagram, options, resume) {
_.defaults(options, {
'css-class': 'simple',
'font-size': 16,
'font-family': 'Andale Mono, monospace'
});
this.init(diagram, options, resume);
};
_.extend(SnapTheme.prototype, BaseTheme.prototype, {
init: function(diagram, options, resume) {
BaseTheme.prototype.init.call(this, diagram);
this.paper_ = undefined;
this.cssClass_ = options['css-class'] || undefined;
this.font_ = {
'font-size': options['font-size'],
'font-family': options['font-family']
};
var a = this.arrowTypes_ = {};
a[ARROWTYPE.FILLED] = 'Block';
a[ARROWTYPE.OPEN] = 'Open';
var l = this.lineTypes_ = {};
l[LINETYPE.SOLID] = '';
l[LINETYPE.DOTTED] = '6,2';
var that = this;
this.waitForFont(function() {
resume(that);
});
},
// Wait for loading of the font
waitForFont: function(callback) {
var fontFamily = this.font_['font-family'];
if (typeof WebFont == 'undefined') {
throw new Error('WebFont is required (https://github.com/typekit/webfontloader).');
}
if (LOADED_FONTS[fontFamily]) {
// If already loaded, just return instantly.
callback();
return;
}
WebFont.load({
custom: {
families: [fontFamily] // TODO replace this with something that reads the css
},
classes: false, // No need to place classes on the DOM, just use JS Events
active: function() {
LOADED_FONTS[fontFamily] = true;
callback();
},
inactive: function() {
// If we fail to fetch the font, still continue.
LOADED_FONTS[fontFamily] = true;
callback();
}
});
},
addDescription: function(svg, description) {
var desc = document.createElementNS(xmlns, 'desc');
desc.appendChild(document.createTextNode(description));
svg.appendChild(desc);
},
setupPaper: function(container) {
// Container must be a SVG element. We assume it's a div, so lets create a SVG and insert
var svg = document.createElementNS(xmlns, 'svg');
container.appendChild(svg);
this.addDescription(svg, this.diagram.title || '');
this.paper_ = Snap(svg);
this.paper_.addClass('sequence');
if (this.cssClass_) {
this.paper_.addClass(this.cssClass_);
}
this.beginGroup();
// TODO Perhaps only include the markers if we actually use them.
var a = this.arrowMarkers_ = {};
var arrow = this.paper_.path('M 0 0 L 5 2.5 L 0 5 z');
a[ARROWTYPE.FILLED] = arrow.marker(0, 0, 5, 5, 5, 2.5)
.attr({id: 'markerArrowBlock'});
arrow = this.paper_.path('M 9.6,8 1.92,16 0,13.7 5.76,8 0,2.286 1.92,0 9.6,8 z');
a[ARROWTYPE.OPEN] = arrow.marker(0, 0, 9.6, 16, 9.6, 8)
.attr({markerWidth: '4', id: 'markerArrowOpen'});
},
layout: function() {
BaseTheme.prototype.layout.call(this);
this.paper_.attr({
width: this.diagram.width + 'px',
height: this.diagram.height + 'px'
});
},
textBBox: function(text, font) {
// TODO getBBox will return the bounds with any whitespace/kerning. This makes some of our aligments screwed up
var t = this.createText(text, font);
var bb = t.getBBox();
t.remove();
return bb;
},
// For each drawn element, push onto the stack, so it can be wrapped in a single outer element
pushToStack: function(element) {
this._stack.push(element);
return element;
},
// Begin a group of elements
beginGroup: function() {
this._stack = [];
},
// Finishes the group, and returns the <group> element
finishGroup: function() {
var g = this.paper_.group.apply(this.paper_, this._stack);
this.beginGroup(); // Reset the group
return g;
},
createText: function(text, font) {
text = _.invoke(text.split('\n'), 'trim');
var t = this.paper_.text(0, 0, text);
t.attr(font || {});
if (text.length > 1) {
// Every row after the first, set tspan to be 1.2em below the previous line
t.selectAll('tspan:nth-child(n+2)').attr({
dy: '1.2em',
x: 0
});
}
return t;
},
drawLine: function(x1, y1, x2, y2, linetype, arrowhead) {
var line = this.paper_.line(x1, y1, x2, y2).attr(LINE);
if (linetype !== undefined) {
line.attr('strokeDasharray', this.lineTypes_[linetype]);
}
if (arrowhead !== undefined) {
line.attr('markerEnd', this.arrowMarkers_[arrowhead]);
}
return this.pushToStack(line);
},
drawRect: function(x, y, w, h) {
var rect = this.paper_.rect(x, y, w, h).attr(RECT);
return this.pushToStack(rect);
},
/**
* Draws text with a optional white background
* x,y (int) x,y top left point of the text, or the center of the text (depending on align param)
* text (string) text to print
* font (Object)
* align (string) ALIGN_LEFT or ALIGN_CENTER
*/
drawText: function(x, y, text, font, align) {
var t = this.createText(text, font);
var bb = t.getBBox();
if (align == ALIGN_CENTER) {
x = x - bb.width / 2;
y = y - bb.height / 2;
}
// Now move th