openct-tasks/_common/modules/pemFioi/LR_parser-1.0.js

4836 lines
172 KiB
JavaScript

function LR_Parser(settings,subTask,answer) {
self = this;
this.strings = {
explanations: {
// +
shift: 'The lookahead symbol {symbol} is read from the input and state {state} is pushed onto the stack.',
//
reduce1: 'Lookahead symbol {symbol} is in the Follow set of the LHS non-terminal ({non_terminal}) for the item {item}, thus states {popped_states} are popped from the stack that represent {RHS} in the derivation.',
reduce2: 'The top element after popping {popped_states} is {top_state}, that leads to state {new_state} with the non-terminal {non_terminal}, which is pushed onto the stack.',
//+
error: 'No shift or reduce operations possible at state {state} for lookahead symbol {symbol}',
//?
not_accepted: 'The input is fully read, and no reduction is possible, so the input is in the language of the grammar.',
//+
accepted: 'The input is fully read, and the current state {state} has the item {base_reduction_item}, so the input is accepted.'
}
}
this.formatExplanation = function(key, values) {
var str = this.strings.explanations[key] || null;
if(str === null) {
console.error('Explanation ' + key + ' not found.');
return str;
}
values = values || {};
for(var i in values) {
if(!values.hasOwnProperty(i)) {
continue;
}
str = str.replace('{' + i + '}', '<strong>' + values[i] + '</strong>');
}
return str;
}
this.mode = settings.mode;
/*
1: simulation
2: execute existing automaton
3: create automaton
4: create parse table
5: execute existing automaton with parse table
6: derivation tree bottom up
7: derivation tree top down
*/
this.rules = settings.rules;
this.input = settings.input;
this.grammar;
this.lrClosureTable;
this.lrTable;
this.stackData = ["State","Symbol"];
this.stack = [["0", "#"]];
this.currentState;
this.currentVertex;
this.inputIndex = 0;
this.actionSequence = [];
this.simulationStep = 0;
this.computedStates = {}; // mode 3
this.derivationTree = [];
this.treeHeight = 0;
this.treePaper;
this.treeElements = {};
this.treeCharSize = 20 // mode > 6
this.treeClickableElements = {};
this.treeSelectionMarker = null;
this.inputBaseline = []; // mode = 7
this.selectedSymbolIndices = [];
this.reductionMarkerR = 10;
this.timeOutID;
this.animationTime = 1000;
this.token;
this.divID = settings.divID,
this.parseInfoID = "parseInfo";
this.parseTableID = "parseTable";
this.graphPaperID = "graphPaper";
this.tabsID = "tabs";
this.tabsContainerID = "tabsCont";
this.sideTable = false;
this.rowHL = null;
this.colHL = null;
this.gotoColHL = null;
this.removeGotoColHL = false; // simulation bug fix with parse table anim
this.paper;
this.paperHeight = settings.paperHeight;
this.paperWidth = settings.paperWidth;
this.visualGraphJSON = settings.visualGraphJSON;
this.stackPreview;
this.stackPreviewElements;
var stackPreviewW = 50;
var stackPreviewH;
this.graphDrawer;
this.visualGraph;
this.graph;
this.graphMouse;
this.graphEditor;
this.isLastActionAGraphEdit = true;
this.isDragging = false;
this.isAnimationRunning = false;
this.waitingForGoto = false; // used only in mode 5 && 2
this.tabTag = [ "automatonTab", "parseTableTab" ];
this.selectedTab = (this.mode != 4) ? 0 : 1;
this.selectedVertex = null;
this.selectedRule = null;
this.selectedStackElements = [];
this.inputHighlight = null;
this.prevStateHighlight = null;
this.pathHighlight = {};
this.stackElementsHL = [];
this.reductionClickArea = {}; // for clicking lines in the automaton in mode 2
this.reductionStates = []; // states with a reduction marker
this.cellEditor = null;
this.accept = false;
this.error = false;
if(/Linux/.test(window.navigator.platform)){
var arrow = "→";
var dot = "・";
}else{
var arrow = "🡒";
var dot = "🞄";
}
this.colors = {
black: "#4a4a4a",
yellow: "#f7aa28",
lightgrey: "#f2f2f2",
blue: "#4990e2",
lightBlue: "#cbddf3",
greyBlue: "#aec6e2"
};
this.unselectedTabAttr = {
opacity: 0.5
};
this.selectedTabAttr = {
opacity: 1
};
this.defaultEdgeAttr = {
"stroke": this.colors.yellow,
"stroke-width": 4,
"arrow-end": "long-classic-wide"
};
this.defaultVertexAttr = {
"r": 10,
"fill": this.colors.black,
"stroke": "white",
"stroke-width": 1
};
this.headerAttr = {
r: 10,
fill: "none",
stroke: "white",
"stroke-width": 1
};
this.defaultSelectedVertexAttr = {
stroke: this.colors.yellow,
"stroke-width": 5
};
this.defaultSelectedEdgeAttr = {
"stroke": this.colors.yellow,
"stroke-width": 6,
};
this.defaultCurrentStateAttr = {
// "fill": this.colors.blue,
stroke: this.colors.blue,
"stroke-width": 5
};
this.currentHeaderAttr = {
"fill": this.colors.blue,
stroke: "none"
};
this.defaultVertexLabelAttr = {
"font-size": 12,
"fill": "white"
};
this.defaultVertexContentAttr = {
"font-size": 12,
"fill": "white",
"text-anchor": "start"
};
this.previousStateAttr = {
"stroke": this.colors.lightBlue,
"stroke-width": 5,
"fill": "none",
// opacity: "0.1"
};
this.edgeHighlightAttr = {
bwd: {
stroke: this.colors.lightBlue,
"stroke-width": 4
},
fwd: {
stroke: this.colors.blue,
"stroke-width": 4
}
};
this.cellAttr = {
"background-color": this.colors.lightgrey,
color: this.colors.black,
border: "1px solid "+this.colors.black
};
this.reductionMarkerAttr = {
circle: {
stroke: "white",
fill: this.colors.black,
r: this.reductionMarkerR
},
text: {
"font-size": 12,
"font-weight": "bold",
// fill: this.colors.black,
fill: "white",
"text-anchor": "end"
},
rule: {
"font-size": 12,
"font-weight": "bold",
fill: this.colors.yellow,
"text-anchor": "start"
},
acc: {
"font-size": 12,
"font-weight": "bold",
fill: this.colors.yellow
}
};
// this.selectedCellAttr = {
// "background-color": this.colors.blue,
// color: "white",
// border: "1px solid "+this.colors.lightgrey
// };
this.cellHighlightAttr = {
position: "absolute",
border: "4px solid",
"border-color": this.colors.blue,
"pointer-events": "none"
};
this.treeLineAttr = {
stroke: this.colors.yellow,
"stroke-width": 2
};
this.treeCharAttr = {
"font-size": this.treeCharSize * 0.8,
"font-weight": "bold",
"fill": this.colors.blue
};
this.treeSelectionMarkerAttr = {
fill: this.colors.blue,
stroke: "none"
};
this.reductionClickAreaAttr = {
stroke: "none",
fill: "red",
opacity: 0,
cursor: "pointer"
};
this.stackPreviewAttr = {
colW: 50,
background: {
stroke: this.colors.black,
fill: this.colors.lightgrey
},
circle: {
r: 5,
stroke: "none",
fill: this.colors.black
},
line: {
stroke: this.colors.black,
"stroke-width": 2
},
symbolCircle: {
r: 10,
stroke: this.colors.black,
fill: this.colors.lightgrey
},
symbol: {
"font-size": 12,
"font-weight": "bold",
fill: this.colors.black
},
previousCircle: {
fill: this.colors.greyBlue
},
previousLine: {
stroke: this.colors.greyBlue
},
previousSymbolCircle: {
stroke: this.colors.greyBlue
},
previousSymbol: {
fill: this.colors.greyBlue
}
};
this.vertexAttr = settings.vertexAttr || this.defaultVertexAttr;
this.edgeAttr = settings.edgeAttr || this.defaultEdgeAttr;
this.vertexLabelAttr = settings.vertexLabelAttr || this.defaultVertexLabelAttr;
this.vertexContentAttr = settings.vertexContentAttr || this.defaultVertexContentAttr;
this.selectedVertexAttr = settings.selectedVertexAttr || this.defaultSelectedVertexAttr;
this.selectedEdgeAttr = settings.selectedEdgeAttr || this.defaultSelectedEdgeAttr;
this.init = function() {
if(window.FontsLoader) {
FontsLoader.loadFonts(['fontawesome']);
FontsLoader.checkFonts();
}
var html = "";
if(this.mode < 6){
html += "<div id=\""+this.tabsID+"\"></div>";
html += "<div id=\""+this.tabsContainerID+"\">";
html += "<div id=\""+this.graphPaperID+"\"></div>";
}
html += "<div id=\""+this.parseTableID+"\"></div>";
html += "</div>";
html += "<div id=\""+this.parseInfoID+"\"></div>";
$("#"+this.divID).html(html);
this.initParser();
if(this.mode < 6){
if(this.mode != 2){
this.initTabs();
}
this.initAutomata();
if(this.mode != 3){
this.initActionSequence(false,true);
this.initParseTable();
}else{
this.initParseTable();
// this.initActionSequence();
this.initActionSequence(false,true);
}
// this.initParseTable();
this.showTab();
}
this.initParseInfo();
this.style();
if(this.mode < 6){
this.updateState(false,"init");
}
this.initHandlers();
if(this.mode >= 3 && this.mode < 6){
this.onResize();
}
};
this.initTabs = function() {
$("#"+this.tabsID).html("<span id=\"automatonTab\">Automaton</span><div id=\"switchContainer\"><div id=\"switch\"></div></div><span id=\"parseTableTab\">Parse table</span>");
$("#"+this.tabTag[this.selectedTab]).addClass("selectedTab");
};
this.initParser = function() {
var ruleStr = this.rules.toString().replace(/,/g,"\n");
this.grammar = new Grammar(ruleStr);
this.lrClosureTable = new LRClosureTable(this.grammar);
this.lrTable = new LRTable(this.lrClosureTable);
// console.log(this.grammar)
// console.log(this.lrClosureTable)
};
this.initAutomata = function() {
if(!this.paper){
this.paper = subTask.raphaelFactory.create(this.graphPaperID,this.graphPaperID,this.paperWidth,this.paperHeight);
}
if(this.mode < 6 && this.mode != 3){
this.reductionStates = this.getReductionStates();
}
if(!this.graphDrawer){
this.graphDrawer = new SimpleGraphDrawer(this.vertexAttr,this.edgeAttr,null,true);
this.graphDrawer.setVertexLabelAttr(this.vertexLabelAttr);
this.graphDrawer.setVertexContentAttr(this.vertexContentAttr);
this.graphDrawer.setDrawVertex(this.drawVertex);
}
if(this.visualGraph){
this.visualGraph.remove();
}
this.visualGraph = VisualGraph.fromJSON(this.visualGraphJSON, "visualGraph", this.paper, null, this.graphDrawer, true);
this.graph = this.visualGraph.graph;
if(this.mode != 1 && this.mode != 4 && this.mode != 5){
this.graphMouse = new GraphMouse("graphMouse",this.graph,this.visualGraph);
var graphEditorSettings = {
paper: this.paper,
graph: this.graph,
paperElementID: this.graphPaperID,
visualGraph: this.visualGraph,
graphMouse: this.graphMouse,
// alphabet: this.alphabet,
selectedVertexAttr: this.selectedVertexAttr,
selectedEdgeAttr: this.selectedEdgeAttr,
enabled: true
};
if(this.mode == 2){
graphEditorSettings.selectVertexCallback = this.onVertexSelect;
graphEditorSettings.startDragCallbackCallback = this.startDragCallback;
}
if(this.mode == 3){
graphEditorSettings.dragThreshold = 10;
graphEditorSettings.edgeThreshold = 20;
graphEditorSettings.dragLimits = {
minX: this.visualGraph.graphDrawer.circleAttr.r,
maxX: this.paper.width - this.visualGraph.graphDrawer.circleAttr.r,
minY: this.visualGraph.graphDrawer.circleAttr.r,
maxY: this.paper.height - this.visualGraph.graphDrawer.circleAttr.r
};
graphEditorSettings.callback = this.graphEditorCallback;
graphEditorSettings.selectVertexCallback = this.selectVertexCallback;
graphEditorSettings.startDragCallbackCallback = this.startDragCallback;
graphEditorSettings.moveDragCallback = this.moveDragCallback;
graphEditorSettings.contentValidation = this.contentValidation;
graphEditorSettings.vertexLabelValidation = this.vertexLabelValidation;
graphEditorSettings.writeContentCallback = this.writeContentCallback;
graphEditorSettings.resizeTableVertexCallback = this.resizeTableVertexCallback;
graphEditorSettings.onDragEnd = this.graphEditorCallback;
graphEditorSettings.vertexLabelPrefix = "";
}
if(this.graphEditor){
this.graphEditor.setEnabled(false);
this.graphEditor = null;
}
this.graphEditor = new GraphEditor(graphEditorSettings);
if(this.mode == 2){
this.graphEditor.setCreateVertexEnabled(false);
this.graphEditor.setCreateEdgeEnabled(false);
this.graphEditor.setVertexDragEnabled(false);
this.graphEditor.setEdgeDragEnabled(false);
this.graphEditor.setGraphDragEnabled(false);
this.graphEditor.setScaleGraphEnabled(false);
this.graphEditor.setEditVertexLabelEnabled(false);
this.graphEditor.setEditVertexContentEnabled(false);
this.graphEditor.setEditEdgeLabelEnabled(false);
this.graphEditor.setTerminalEnabled(false);
this.graphEditor.setInitialEnabled(false);
}else if(this.mode == 3){
this.paper.rect(1,1,this.paperWidth - 2, this.paperHeight - 2);
this.graphEditor.setTableMode(true);
this.graphEditor.setMultipleEdgesEnabled(false);
this.graphEditor.setLoopEnabled(false);
// this.graphEditor.setInitialEnabled(false);
this.graphEditor.setAllowMultipleTerminal(false);
this.graphEditor.setAllowMultipleInitial(false);
this.graphEditor.setAllowSimultaneousInitialAndTerminal(false);
this.graphEditor.graphDragger.setMoveDragCallback(this.graphDraggerMoveDragCallback);
}
this.graphEditor.setIconAttr({fill:this.colors.yellow,stroke:"none"});
}
this.formatContent();
this.visualGraph.redraw();
if(this.graphEditor){
this.graphEditor.updateHandlers();
}
};
this.initActionSequence = function(validation,tree) {
if(this.actionSequence){
this.actionSequence = [];
}
if(this.input.charAt(this.input.length - 1) != "$"){
this.input += "$";
}
this.input = this.input.replace(/ /g,"");
var state = this.getInitialState();
if(state == null){
return
}
var iChar = 0;
var symbol = this.input.charAt(iChar);
var error = false;
var success = false;
var nLoop = 0;
var treeIndex = 0;
// console.log(this.lrTable.states);
do{
nLoop++;
if(this.mode != 3){
var action = this.lrTable.states[state][symbol];
}else{
// console.log(state)
for(var compState in this.computedStates){
if(compState == state){
var action = this.computedStates[compState][symbol];
break;
}
}
}
// console.log(action);
if(action /*&& (!this.mode == 3 || this.doesAutomatonAllowAction(state,symbol,action))*/ /*|| tree*/){
switch(action[0].actionType){
case "s":
this.actionSequence.push({
actionType: "s",
state: action[0].actionValue,
// char: symbol
});
state = action[0].actionValue;
this.stack.push([state,symbol]);
iChar++;
symbol = this.input.charAt(iChar);
break;
case "r":
var ruleIndex = action[0].actionValue;
this.actionSequence.push({
actionType: "r",
rule: ruleIndex
});
symbol = this.grammar.rules[ruleIndex].nonterminal;
nbRedChar = (this.grammar.rules[ruleIndex].development[0] == "''") ? 0 : this.grammar.rules[ruleIndex].development.length;
state = this.stack[this.stack.length - 1 - nbRedChar][0];
if(nbRedChar > 0){
this.stack.splice(this.stack.length - nbRedChar,nbRedChar,symbol);
}else{
this.stack.push(symbol);
}
if(symbol == "S" && iChar >= this.input.length - 1){
this.actionSequence[this.actionSequence.length - 1]["goto"] = this.getTerminalState();
success = true;
}
/* create derivation tree */
if(tree){
var inputIndex = iChar - 1;
if(nbRedChar == 0){ // to deal with empty development
treeIndex = 2*inputIndex + 2;
}else{
// console.log(treeIndex);
treeIndex = (2*inputIndex + 1 >= treeIndex) ? 2*inputIndex + 1 : treeIndex;
}
if(nbRedChar <= 1){
if(this.derivationTree[treeIndex]){
this.derivationTree[treeIndex].push([ruleIndex,this.actionSequence.length - 1]);
}else{
this.derivationTree[treeIndex] = [[ruleIndex,this.actionSequence.length - 1]];
}
if(this.derivationTree[treeIndex].length > this.treeHeight){
this.treeHeight = this.derivationTree[treeIndex].length;
}
}else{
// console.log(treeIndex+" "+ruleIndex);
var nodeHeight = 0;
var childrenIndices = [treeIndex];
var iCol = treeIndex;
do{
iCol--;
if(this.derivationTree[iCol] || iCol%2 == 1){
childrenIndices.unshift(iCol);
}
}while(childrenIndices.length < nbRedChar && iCol >= 0);
for(var i of childrenIndices){
var branchLength = (this.derivationTree[i]) ? this.derivationTree[i].length : 0;
if(branchLength > nodeHeight){
nodeHeight = branchLength;
}
}
for(var i of childrenIndices){
var branchLength = (this.derivationTree[i]) ? this.derivationTree[i].length : 0;
if(branchLength != nodeHeight){
var gap = nodeHeight - branchLength;
for(var j = 0; j < gap; j++){
if(!this.derivationTree[i]){
this.derivationTree[i] = [];
}
this.derivationTree[i].push(["",this.actionSequence.length - 1]);
}
}
if(i == treeIndex){
this.derivationTree[i].push([ruleIndex,this.actionSequence.length - 1]);
}
if(this.derivationTree[i].length > this.treeHeight){
this.treeHeight = this.derivationTree[i].length;
}
}
}
}
break;
case "": // goto
state = action[0].actionValue;
this.actionSequence[this.actionSequence.length - 1]["goto"] = state;
symbol = this.input.charAt(iChar);
break;
}
}else{
error = true;
this.actionSequence.push({actionType: "error"});
break;
}
}while(iChar < this.input.length && !error && !success && nLoop < 50);
this.stack = [[this.getInitialState(),"#"]];
// console.log(this.derivationTree);
// console.log(this.actionSequence);
if((this.mode == 2 || this.mode == 5) && !validation){
this.actionSequence = [];
}
};
this.initParseTable = function() {
// console.log("initParseTable")
var colLabel = [];
var terminalStateIndex = this.lrTable.states.length;
var html = "<table><tr><th rowspan=2>State</th><th colspan=\""+(this.grammar.terminals.length + 1)+"\">Action</th><th colspan=\""+this.grammar.nonterminals.length+"\">Goto</th></tr>";
html += "<tr>";
for(var terminal of this.grammar.terminals){
html += "<th>"+terminal+"</th>";
colLabel.push(terminal);
}
html += "<th>$</th>";
colLabel.push("$");
for(var nonterminal of this.grammar.nonterminals){
html += "<th>"+nonterminal+"</th>";
colLabel.push(nonterminal);
}
html += "</tr>";
if(this.mode != 3){
for(var iState = 0; iState <= this.lrTable.states.length; iState++){
var state = this.lrTable.states[iState];
var stateID = (state) ? state.index : terminalStateIndex;
html += "<tr>";
html += "<td data_state=\""+stateID+"\">"+stateID+"</td>";
for(var iCol = 0; iCol < (this.grammar.terminals.length + 1 + this.grammar.nonterminals.length); iCol++){
// html += (stateID == terminalStateIndex) ? "<td>" : "<td data_state=\""+stateID+"\" data_symbol=\""+colLabel[iCol]+"\">";
html += "<td data_state=\""+stateID+"\" data_symbol=\""+colLabel[iCol]+"\">";
if(this.mode != 4){
if(state && state[colLabel[iCol]]){
var actionType = state[colLabel[iCol]][0]["actionType"];
var actionValue = state[colLabel[iCol]][0]["actionValue"];
if(actionType == "r"){
var displayedRule = actionValue + 1;
html += "<span class=\"ruleMarker\">"+actionType+"<span class=\"ruleMarkerIndex\">"+displayedRule+"</span>"+"</span>";
}else{
html += actionType+actionValue;
}
}else if(colLabel[iCol] == "S" && stateID == 0){
html += terminalStateIndex;
}else if(colLabel[iCol] == "$" && stateID == terminalStateIndex){
html += "<span class=\"ruleMarker ruleMarkerIndex\">acc.</span>";
}
}
html += "</td>";
}
html += "</tr>";
}
}
html += "</table>";
$("#"+this.parseTableID).append(html);
if(this.mode == 3){
this.initStackPreview();
this.updateParseTable();
}else{
this.initStackPreview();
}
};
this.initStackPreview = function() {
$("#"+this.parseTableID).prepend("<div id=\"stackPreview\"></div>");
stackPreviewH = $("#"+this.parseTableID).height();
this.stackPreview = subTask.raphaelFactory.create("stackPreview","stackPreview",stackPreviewW,stackPreviewH);
};
this.initParseInfo = function() {
var html = "<div id=\"rules\"></div><div id=\"action\"></div>";
$("#"+this.parseInfoID).html(html);
this.initRules();
this.initAction();
};
this.initRules = function() {
var html = "<h3>GRAMMAR</h3>";
html += "<ul>";
for(var iRule = 0; iRule < this.rules.length; iRule++){
var development = (this.grammar.rules[iRule].development[0] == "''") ? "<span class=\"epsilon\">ε</span>" : this.grammar.rules[iRule].development.join(" ");
var displayedRule = iRule + 1;
html += "<li class=\"rule\" data_rule=\""+iRule+"\"><span class=\"ruleIndex\">"+displayedRule+
"</span> <span class=\"nonTerminal\">"+this.grammar.rules[iRule].nonterminal+"</span><i class=\"fas fa-long-arrow-alt-right\"></i><span class=\"development\">"+
development+"</span></li>";
}
html += "</ul>";
$("#rules").html(html);
};
this.initAction = function() {
if(this.mode < 6){
this.initPlayer();
this.initExplanations();
}
this.initActionInfo();
};
this.initPlayer = function() {
var html = "<div id=\"player\">"
html += "<div id=\"play\"><i class=\"fas fa-play\"></i></div>";
html += "<div id=\"progressBarClickArea\">";
html += "<div id=\"progressBarContainer\">";
html += "<div id=\"progressBar\"><div id=\"progressBarMarker\"></div></div>"
html += "</div></div>";
html += "<div id=\"stepBackward\"><i class=\"fas fa-step-backward\"></i></div>";
html += "<div id=\"stepForward\"><i class=\"fas fa-step-forward\"></i></div>";
if(this.mode == 3){
html += "<div id=\"undo\">UNDO</div>";
}
html += "</div>";
// html += "<div id=\"actionInfo\"></div>";
$("#action").html(html);
};
this.initExplanations = function() {
var el = $('#lr-explanation');
if(el.length) {
el.html('');
} else {
var div = $("<div id=\"lr-explanation\"></div>").css('margin-top', '0.5em');
$("#action").append(div);
}
}
/*
this.displayExplanation('reduce1', {
symbol: this.input.charAt(this.inputIndex),
non_terminal: nonTerminal,
item: this.grammar.rules[this.selectedRule].toString(),
popped_states: prevStates.slice(1).join(', '),
RHS: 'TODO'
});
*/
this.displayExplanation = function(key, values) {
return;
$('#lr-explanation').html(key ? this.formatExplanation(key, values) : '');
}
this.initActionInfo = function() {
var html = "<div id=\"actionInfo\"></div>";
$("#action").append(html);
if(this.mode < 6){
this.initStackTable();
this.initReduceButton();
this.initInput();
this.initShiftButton();
this.initAcceptButton();
this.initErrorButton();
}
this.initDerivationTree();
if(this.mode >= 6){
this.initButtons();
}
if(this.mode == 7){
this.initObjective();
}
};
this.initStackTable = function() {
var html = "<h4>Stack</h4>";
html += "<div id=\"stackTableContainer\">"; // to deal with stack highlight
html += "<table id=\"stackTable\">";
html += "</table>";
html += "</div>";
$("#actionInfo").html(html);
this.updateStackTable();
};
this.initButtons = function() {
var html = "<div id=\"buttons\"></div>";
$("#tree").append(html);
this.initReduceButton();
this.initUndoButton();
};
this.initReduceButton = function() {
if(this.mode < 6){
var html = "<div id=\"reduceBar\">";
html += "<div class=\"messageBackground\"><div id=\"reduceMessage\" class=\"actionMessage\"></div></div>"
html += "<div id=\"reduceButton\" class=\"actionButton\"><i class=\"fas fa-compress buttonIcon\"></i> <span>REDUCE</span></div>"
html += "</div>";
$("#actionInfo").append(html);
}else if (this.mode == 6){
var html = "<div id=\"reduceButton\" class=\"actionButton\"><i class=\"fas fa-compress buttonIcon\"></i> REDUCE</div>";
$("#buttons").append(html);
}else if(this.mode == 7){
var html = "<div id=\"produceButton\" class=\"actionButton\"><i class=\"fas fa-expand buttonIcon\"></i> PRODUCE</div>";
$("#buttons").append(html);
}
};
this.initUndoButton = function() {
var html = "<div id=\"undo\" class=\"actionButton\"><i class=\"fas fa-undo-alt buttonIcon\"></i> UNDO</div>";
$("#buttons").append(html);
};
this.initInput = function() {
if(this.input.charAt(this.input.length - 1) != "$"){
this.input += "$";
}
this.input = this.input.replace(/ /g,"");
var html = "<div id=\"inputBar\">";
html += "<h4>Input</h4>";
for(var char of this.input){
html += "<div class=\"inputChar\">"+char+"</div>";
}
html += "</div>";
$("#actionInfo").append(html);
this.initCursor();
};
this.initCursor = function() {
var html = "<div id=\"cursor\"><div id=\"topCircle\"></div><div id=\"cursorBar\"></div><div id=\"bottomCircle\"></div></div>";
$("#inputBar").append(html);
};
this.initShiftButton = function() {
var html = "<div id=\"shiftBar\">";
html += "<div class=\"messageBackground\"><div id=\"shiftMessage\" class=\"actionMessage\"></div></div>"
html += "<div id=\"shiftButton\" class=\"actionButton\"><i class=\"fas fa-arrow-right buttonIcon\"></i> SHIFT</div>"
html += "</div>";
$("#actionInfo").append(html);
};
this.initAcceptButton = function(){
var html = "<div id=\"acceptButton\" class=\"actionButton\"><i class=\"fas fa-thumbs-up buttonIcon\"></i> ACCEPT</div>";
$("#actionInfo").append(html);
};
this.initErrorButton = function(){
var html = "<div id=\"errorButton\" class=\"actionButton\"><i class=\"fas fa-times buttonIcon\"></i> ERROR</div>";
$("#actionInfo").append(html);
};
this.initDerivationTree = function() {
// console.log("initDerivationTree");
var html = "<div id=\"derivationTree\">";
html += "<h4>Derivation Tree</h4>";
html += "<div id=\"tree\"></div>";
if(this.mode < 6){
html += "<div id=\"treeCursor\"></div>";
}
html += "</div>";
$("#actionInfo").append(html);
if(this.mode >= 6){
$("#tree").append($("<div id=\"treePaper\"></div>"));
this.derivationTree = {};
this.input = this.input.replace(/ /g,"");
if(this.mode == 6){
for(var iChar = 0; iChar < this.input.length; iChar++){
this.derivationTree[2 * iChar + 1] = this.input.charAt(iChar);
}
}else if(this.mode == 7){
this.derivationTree[this.input.length] = this.grammar.rules[0].nonterminal;
}
this.updateTree();
}
};
this.initHandlers = function() {
if(this.mode < 6 && this.mode != 2){
$("#"+this.tabsID+" #switchContainer").off("click");
$("#"+this.tabsID+" #switchContainer").click(self.switchTab);
$(window).resize(self.onResize);
}
if(this.mode < 6 && this.mode > 2 /*&& this.mode != 3*/){
$("#stackPreview").hide();
$("#"+this.parseTableID+" table").hover(
function(){
$("#stackPreview").show();
},function(){
$("#stackPreview").hide();
});
}
switch(this.mode){
case 2:
$("#reduceButton").off("click");
$("#reduceButton").click(self.reduce);
// $("#shiftButton").off("click");
// $("#shiftButton").click(self.shift);
this.enableShiftButton();
$(".stackElement").off("click");
$(".stackElement").click(self.selectStackElement);
$(".rule").off("click");
$(".rule").click(self.clickRule);
$(".rule, .actionButton, #stackTable .stackElement").css({
cursor: "pointer"
});
this.initPlayerHandlers();
this.enableAcceptButton();
// $("#acceptButton").off("click");
// $("#acceptButton").click(self.acceptInput);
$("#errorButton").off("click");
$("#errorButton").click(self.refuseInput);
break;
case 3:
this.initPlayerHandlers();
this.disableUndoButton();
break;
case 4:
$("#"+this.parseTableID+" td[data_state]").off("click");
$("#"+this.parseTableID+" td[data_state]").click(self.clickCell);
this.initPlayerHandlers();
break;
case 5:
this.initPlayerHandlers();
$("#acceptButton").off("click");
$("#acceptButton").click(self.acceptInput);
$("#errorButton").off("click");
$("#errorButton").click(self.refuseInput);
$("#"+this.parseTableID+" td[data_state]").off("click");
$("#"+this.parseTableID+" td[data_state]").click(self.clickCell);
$("#"+this.divID).off("click");
$("#"+this.divID).click(self.resetFeedback);
break;
case 6:
case 7:
for(var iEl in this.treeClickableElements){
var el = this.treeClickableElements[iEl];
el.raphObj.attr("cursor","pointer");
if(this.mode == 6){
el.raphObj.unmousedown();
el.raphObj.mousedown(self.selectSymbol(iEl));
}else{
el.raphObj.unclick();
el.raphObj.click(self.selectSymbol(iEl));
}
}
$(".rule").off("click");
$(".rule").click(self.clickRule);
$("#reduceButton, #produceButton").off("click");
$("#reduceButton").click(self.reduce);
$("#produceButton").click(self.produce);
$(".rule, #reduceButton, #produceButton").css({
cursor: "pointer"
});
break;
default:
this.initPlayerHandlers();
}
};
this.initPlayerHandlers = function() {
$("#play").off("click");
$("#play").click(self.runSimulation);
$("#play").css({
cursor: "pointer"
});
this.disablePlayerSteps();
this.enablePlayerSteps();
this.disableProgressBarClick();
this.enableProgressBarClick();
};
this.initObjective = function() {
var html = "<div id=\"objective\"><h4>Objective</h4><div id=\"string\"></div></div>";
$("#actionInfo").append(html);
var input = this.input.split('');
for(var char of input){
$("#objective #string").append("<div class=\"inputChar\">"+char+"</div>");
}
};
this.disablePlayerSteps = function() {
$("#stepBackward").off("click");
$("#stepForward").off("click");
$("#stepBackward, #stepForward").css({
cursor: "auto",
opacity: "0.5"
});
};
this.enablePlayerSteps = function() {
$("#stepBackward").click(self.stepBackward);
$("#stepForward").click(self.stepForward);
$("#stepBackward, #stepForward").css({
cursor: "pointer",
opacity: "1"
});
};
this.disablePlayerStepBack = function() {
$("#stepBackward").off("click");
$("#stepBackward").css({
cursor: "auto",
opacity: "0.5"
});
};
this.enablePlayerStepBack = function() {
$("#stepBackward").click(self.stepBackward);
$("#stepBackward").css({
cursor: "pointer",
opacity: "1"
});
};
this.disableProgressBarClick = function() {
$("#progressBarClickArea").off("mousedown");
$("#player").off("mousemove");
$("#player").off("mouseup");
$("#progressBarClickArea").css({
cursor: "auto"
});
};
this.enableProgressBarClick = function() {
$("#progressBarClickArea").mousedown(self.progressBarDragStart);
$("#player").mousemove(self.progressBarDragMove);
$("#player").mouseup(self.progressBarDragEnd);
$("#progressBarClickArea").css({
cursor: "pointer"
});
};
this.disableShiftButton = function() {
$("#shiftButton").off("click");
$("#shiftButton").css({opacity:0.7,cursor:"auto"});
$("#shiftBar .messageBackground").css({opacity:0.7});
};
this.enableShiftButton = function() {
$("#shiftButton").off("click");
$("#shiftButton").click(this.shift);
$("#shiftButton").css({opacity:1,cursor:"pointer"});
$("#shiftBar .messageBackground").css({opacity:1});
// $("#shiftButton").css("cursor","pointer");
};
this.disableAcceptButton = function() {
$("#acceptButton").off("click");
$("#acceptButton").css({opacity:0.7,cursor:"auto"});
};
this.enableAcceptButton = function() {
$("#acceptButton").off("click");
$("#acceptButton").click(this.acceptInput);
$("#acceptButton").css({opacity:1,cursor:"pointer"});
};
this.disableUndoButton = function() {
$("#undo").off("click");
$("#undo").css({
cursor: "auto",
opacity: "0.5"
})
};
this.enableUndoButton = function() {
$("#undo").off("click");
$("#undo").click(self.undo);
$("#undo").css({
cursor: "pointer",
opacity: "1"
})
};
this.drawVertex = function(id, info, visualInfo) {
var pos = this._getVertexPosition(visualInfo);
this.originalPositions[id] = pos;
var label = (info.label) ? info.label : "";
var content = (info.content) ? info.content : "";
var reductionInfo = false;
var wCorr = 0;
if(self.reductionStates.length > 0){
for(var redData of self.reductionStates){
if(redData.state == label){
/* increase box width for reduction marker */
wCorr = 2*self.reductionMarkerR + 20;
visualInfo.wCorr = wCorr;
reductionInfo = redData;
break;
}
}
}
if(info.terminal && self.mode != 3){
wCorr = 50;
visualInfo.wCorr = wCorr;
reductionInfo = "acc.";
}
var boxSize = this.getBoxSize(content,wCorr);
var w = boxSize.w;
var h = boxSize.h;
var x = pos.x - w/2;
var y = pos.y - h/2;
var labelHeight = 2*this.vertexLabelAttr["font-size"];
var node = this.paper.rect(x,y,w,h).attr(this.rectAttr);
var header = this.paper.rect(x,y,w,labelHeight + 10).attr(this.rectAttr);
header.attr("clip-rect",x+" "+y+" "+w+" "+labelHeight);
// var headerCover = this.paper.rect(x,y + labelHeight,w,10).attr(self.headerCoverAttr);
var labelRaph = this.paper.text(pos.x, y + labelHeight/2, label).attr(this.vertexLabelAttr);
var line = this.paper.path("M"+x+","+(y + labelHeight)+"H"+(x + w)).attr(this.boxLineAttr);
var contentX = pos.x - w/2 + 10;
var content = this.paper.text(contentX, y + labelHeight + (h - labelHeight)/2,content).attr(this.vertexContentAttr);
if(info.initial && !info.terminal){
var initialArrow = this.paper.path("M" + (x - 30) + "," + pos.y + "H" + x).attr(this.lineAttr);
initialArrow.attr("stroke-width",this.lineAttr["stroke-width"]);
var result = [node,labelRaph,line,content,header,initialArrow];
}else if(!info.initial && info.terminal){
var terminalFrame = this.paper.rect(x - 5, y - 5, w + 10, h + 10, this.circleAttr.r + 5);
var result = [node,labelRaph,line,content,header,terminalFrame];
}else if(info.initial && info.terminal){
var terminalFrame = this.paper.rect(x - 5, y - 5, w + 10, h + 10, this.circleAttr.r + 5);
var initialArrow = this.paper.path("M" + (x - 2*this.circleAttr.r) + "," + pos.y + "H" + x).attr(this.lineAttr);
initialArrow.attr("stroke-width",this.lineAttr["stroke-width"]+1);
var result = [node,labelRaph,line,content,header,initialArrow,terminalFrame];
}else{
var result = [node,labelRaph,line,content,header];
}
if(reductionInfo){
if(reductionInfo != "acc."){
var rule = reductionInfo.rule;
}
var attr = self.reductionMarkerAttr;
var textSize = this.getTextSize(info.content);
if(reductionInfo != "acc."){
var x = visualInfo.x + w/2 - self.reductionMarkerR - 10;
}else{
var x = visualInfo.x + w/2 - wCorr/2;
}
if(textSize.nbLines == 1){
var y = content.attr("y");
}else{
var lines = info.content.split("\n");
var redIndex;
for(var iLine = 0; iLine < lines.length; iLine++){
var line = lines[iLine].trim();
for (var i = 0, chr; i < line.length; i++) {
[chr, i] = getWholeCharAndI(line, i);
}
var lastChar = chr;
if(lastChar == "." || lastChar == dot){
redIndex = iLine;
break;
}
}
var textBBox = content.getBBox();
var y = textBBox.y + (textBBox.height/lines.length)*(redIndex + 1/2);
}
if(reductionInfo != "acc."){
var circle = this.paper.circle(x,y,10).attr(attr.circle);
var text = this.paper.text(x - 1,y,"r").attr(attr.text);
var displayedRule = rule + 1;
var ruleObj = this.paper.text(x + 1,y,displayedRule).attr(attr.rule);
if(self.mode == 2){
var textBBox = content.getBBox();
var lines = info.content.split("\n");
var clickAreaX = contentX;
var clickAreaY = y - self.reductionMarkerR;
var clickAreaW = visualInfo.x + w/2 - 10 - contentX;
var clickAreaH = 2*self.reductionMarkerR;
var clickArea = this.paper.rect(clickAreaX,clickAreaY,clickAreaW,clickAreaH).attr(self.reductionClickAreaAttr);
clickArea.click(self.clickReductionMarker(rule,label,id));
if(self.reductionClickArea[id]){
self.reductionClickArea[id].remove();
}
self.reductionClickArea[id] = clickArea;
}
result.push(circle,text,ruleObj);
}else{
var rectW = 35;
var rectH = 2*self.reductionMarkerR;
var circle = this.paper.rect(x - rectW/2,y - rectH/2,rectW,rectH).attr(attr.circle);
var text = this.paper.text(x - 1,y,reductionInfo).attr(attr.acc);
result.push(circle,text);
}
}
this._addCustomElements(id, result);
return result;
};
function getWholeCharAndI(str, i) {
var code = str.charCodeAt(i);
if (Number.isNaN(code)) {
return ''; // Position not found
}
if (code < 0xD800 || code > 0xDFFF) {
return [str.charAt(i), i]; // Normal character, keeping 'i' the same
}
// High surrogate (could change last hex to 0xDB7F to treat high private
// surrogates as single characters)
if (0xD800 <= code && code <= 0xDBFF) {
if (str.length <= (i+1)) {
throw 'High surrogate without following low surrogate';
}
var next = str.charCodeAt(i+1);
if (0xDC00 > next || next > 0xDFFF) {
throw 'High surrogate without following low surrogate';
}
return [str.charAt(i)+str.charAt(i+1), i+1];
}
// Low surrogate (0xDC00 <= code && code <= 0xDFFF)
if (i === 0) {
throw 'Low surrogate without preceding high surrogate';
}
var prev = str.charCodeAt(i-1);
// (could change last hex to 0xDB7F to treat high private surrogates
// as single characters)
if (0xD800 > prev || prev > 0xDBFF) {
throw 'Low surrogate without preceding high surrogate';
}
// Return the next character instead (and increment)
return [str.charAt(i+1), i+1];
};
this.getReductionStates = function() {
/* return array of states containing a reduction marker */
var attr = this.reductionMarkerAttr;
var reductionData = [];
for(var iState = 0; iState <= this.lrTable.states.length; iState++){
var stateData = this.lrTable.states[iState];
for(var symbol in stateData){
if(symbol != "index"){
var action = stateData[symbol][0];
if(action.actionType == "r"){
reductionData.push({ state: iState, rule: action.actionValue });
break;
}
}
}
}
return reductionData;
};
this.onResize = function() {
/* switch between table displays */
// console.log("resize")
// self.updateParseTableHL();
if(self.mode >= 3){
var width = $(window).width();
var sideTable = true;
if(width < 1180){
sideTable = false
}
if(self.sideTable != sideTable){
self.sideTable = sideTable;
self.styleTabSwitch();
self.styleTabs();
if(self.sideTable){
$("#"+self.graphPaperID).show();
$("#"+self.parseTableID).show();
}else{
self.showTab();
}
}
}
self.updateParseTableHL({action:"resize"});
};
this.reset = function() {
this.stack = [[this.getInitialState(), "#"]];
this.inputIndex = 0;
this.updateCursor(false);
this.simulationStep = 0;
this.selectedRule = null;
$(".rule").removeClass("selected");
this.selectedStackElements = [];
this.updateStackTable();
this.updateState(false,"reset");
this.error = false;
this.accept = false;
this.styleRules();
this.styleStackTable();
this.styleProgressBar();
$("#acceptButton, #errorButton").css({
"background-color": this.colors.blue
});
};
this.runSimulation = function() {
// console.log(self.computedStates);
if(self.mode == 3){
if(self.currentState == null){
return
}
self.isLastActionAGraphEdit = false;
self.showUndo();
}
self.clearHighlight();
if(self.mode == 4){
if(!self.checkParseTable()){
return
}
}
$("#play i").removeClass("fa-play").addClass("fa-pause");
$("#play").off("click");
$("#play").click(self.pauseSimulation);
self.resetFeedback();
self.reset();
self.runSimulationLoop(self.simulationStep,true,false,true,true);
if(self.mode == 3){
self.graphEditor.setEnabled(false);
}
};
this.runSimulationLoop = function(step,loop,reverse,anim,progressBarMove) {
// console.log(step+" "+loop+" "+reverse+" "+anim)
this.isAnimationRunning = true;
var progress = (reverse) ? 100*(step)/this.actionSequence.length : 100*(step + 1)/this.actionSequence.length;
var action = this.actionSequence[step];
this.disablePlayerStepBack();
this.disableProgressBarClick();
if(progress <= 100){
if(!anim || action.actionType == "error"){
var animationTime = (step == 0 && this.actionSequence.length <= 1) ? 100 : 0;
this.clearHighlight();
}else{
var animationTime = (action.actionType == "r") ? 4.5*this.animationTime : 1.5*this.animationTime;
}
if(progressBarMove){
var newPos = {width:progress+"%"};
}else{
var newPos = {};
}
$("#progressBar").animate(newPos,animationTime,function(){
self.isAnimationRunning = false;
if(action.actionType != "r" || self.selectedRule == null){
if(reverse){
self.simulationStep--;
}else{
self.simulationStep++;
}
}
if(!loop){
self.enablePlayerStepBack();
self.enableProgressBarClick();
return;
}
self.runSimulationLoop(self.simulationStep,loop,reverse,anim,progressBarMove);
});
}
if(!action){
self.pauseSimulation(null,true);
return;
}
switch(action.actionType){
case "s":
self.applyShift(action.state,reverse,anim);
break;
case "r":
// console.log("r");
self.treeAnim(step,reverse,anim);
self.clearHighlight();
var rule = action.rule;
// console.log(this.grammar.rules[rule])
var nonTerminal = self.grammar.rules[rule].nonterminal;
self.updateParseTableHL({
action: "startReduction",
nonTerminal: nonTerminal,
anim: anim,
rule: rule,
goto: action.goto
});
if(reverse){
this.reverseReduction(rule);
return;
}
var nbRedChar = (self.grammar.rules[rule].development[0] == "''") ? 0 : self.grammar.rules[rule].development.length;
var startIndex = self.stack.length - nbRedChar;
for(var iSelCol = 0; iSelCol < nbRedChar; iSelCol++){
var index = startIndex + iSelCol;
$(".stackElement[data_col="+index+"]").addClass("selected");
self.selectedStackElements.push(index);
}
self.styleStackTable();
self.selectRule($(".rule[data_rule="+rule+"]"));
if(anim){
this.timeOutID = setTimeout(function() {
// var nonTerminal = self.grammar.rules[rule].nonterminal;
var goto = action.goto || self.getPreviousState();
self.applyReduction(nonTerminal,goto,true);
}, self.animationTime);
}else{
// var nonTerminal = self.grammar.rules[rule].nonterminal;
var goto = action.goto;
self.applyReduction(nonTerminal,goto,false);
}
break;
case "error":
self.refuseInput();
break;
}
};
this.treeAnim = function(step,reverse,anim) {
// console.log("tree");
if(!this.treeElements[step]){
return
}
for(var el of this.treeElements[step]){
if(anim && !reverse){
var anim = new Raphael.animation({opacity:1},this.animationTime);
subTask.raphaelFactory.animate("anim",el,anim);
}else if(reverse){
el.attr({opacity:0});
}else{
el.attr({opacity:1});
}
}
};
this.eraseHigherBranches = function(step) {
for(var iStep in self.treeElements){
if(iStep > step){
for(var el of self.treeElements[iStep]){
el.attr({opacity:0});
}
}
}
};
this.pauseSimulation = function(ev,end) {
// console.log("pause");
self.isAnimationRunning = false;
if(self.mode == 3){
self.isLastActionAGraphEdit = false;
self.showUndo();
}
if(!end){
clearTimeout(self.timeOutID);
subTask.raphaelFactory.stopAnimate("anim");
if($("#progressBar"))
$("#progressBar").stop();
if($(".stackElement"))
$(".stackElement").stop();
if(self.rowHL)
self.rowHL.stop();
if(self.colHL)
self.colHL.stop();
self.replayUpTo(self.simulationStep,false,true);
if(self.token){
self.token.remove();
}
}
$("#play i").removeClass("fa-pause").addClass("fa-play");
self.initPlayerHandlers();
if(self.mode == 3){
self.graphEditor.setEnabled(true);
self.graphEditor.setMultipleEdgesEnabled(false);
self.graphEditor.setLoopEnabled(false);
// self.graphEditor.setInitialEnabled(false);
self.graphEditor.setAllowMultipleTerminal(false);
self.graphEditor.setAllowMultipleInitial(false);
self.graphEditor.setAllowSimultaneousInitialAndTerminal(false);
}
self.resetParseTableHL();
};
this.stepBackward = function() {
if(self.mode == 2){
$("#reduceButton span").text("REDUCE");
$("#reduceButton").off("click");
$("#reduceButton").click(self.reduce);
}
if(self.mode == 3){
self.isLastActionAGraphEdit = false;
self.showUndo();
}
if(self.mode == 4){
if(!self.checkParseTable()){
return
}
}
self.resetFeedback();
if(self.simulationStep < 1){
return;
}else{
self.clearHighlight();
self.replayUpTo(self.simulationStep - 2,false,true);
self.eraseHigherBranches(self.simulationStep - 1);
// self.runSimulationLoop(self.simulationStep - 1, false, true,false,true);
}
};
this.stepForward = function() {
self.resetParseTableHL();
if(self.mode == 3){
self.isLastActionAGraphEdit = false;
self.showUndo();
}
if(self.mode == 4){
if(!self.checkParseTable()){
return
}
}
self.resetFeedback();
if(self.simulationStep >= self.actionSequence.length){
return;
}else{
if(self.isAnimationRunning){
self.pauseSimulation();
}
self.runSimulationLoop(self.simulationStep, false, false,true,true);
}
};
this.progressBarDragStart = function(event) {
if(self.mode == 3){
self.isLastActionAGraphEdit = false;
self.showUndo();
}
self.clearHighlight();
if(self.mode == 4){
if(!self.checkParseTable()){
return
}
}
self.resetFeedback();
self.isDragging = true;
var x = event.pageX - $(this).offset().left;
var w = $(this).width();
var step = Math.floor(self.actionSequence.length*x/w - 0.5);
self.replayUpTo(step,false,false);
$("#progressBar").width(x/w*100+"%");
};
this.progressBarDragMove = function(event) {
if(!self.isDragging){
return
}
var x = event.pageX - $("#progressBarClickArea").offset().left;
var y = event.pageY - $(this).offset().top;
var w = $("#progressBarClickArea").width();
var h = $(this).outerHeight();
if(x < 0 || x > w || y < 10 || y >= h - 10){
self.isDragging = false;
$("#progressBar").width(self.simulationStep/self.actionSequence.length*100+"%");
return
}
var step = Math.floor(self.actionSequence.length*x/w - 0.5);
if(step != self.simulationStep - 1){
self.replayUpTo(step,false,false);
self.eraseHigherBranches(step);
}
$("#progressBar").width(x/w*100+"%");
};
this.progressBarDragEnd = function(event) {
if(!self.isDragging){
return
}
self.isDragging = false;
var x = event.pageX - $("#progressBarClickArea").offset().left;
var w = $("#progressBarClickArea").width();
var step = Math.floor(self.actionSequence.length*x/w - 0.5);
if(step != self.simulationStep){
self.replayUpTo(step,false,false);
self.eraseHigherBranches(step);
}
$("#progressBar").width((step + 1)/self.actionSequence.length*100+"%");
};
this.replayUpTo = function(step,anim,progressBarMove) {
// console.log("replay "+step);
this.reset();
for(var iStep = 0; iStep <= step; iStep++){
this.runSimulationLoop(iStep,false,false,anim,progressBarMove);
}
this.resetParseTableHL();
// this.clearHighlight();
};
this.selectStackElement = function() {
self.resetFeedback();
var col = parseInt($(this).attr("data_col"));
if(col == 0){
return
}
if($(".stackElement[data_col="+col+"]").hasClass("selected")){
for(var iCol = 0; iCol <= col; iCol++){
$(".stackElement[data_col="+iCol+"]").removeClass("selected");
}
self.selectedStackElements = self.selectedStackElements.filter(element => element > col);
}else{
for(var iCol = col; iCol < self.stack.length; iCol++){
$(".stackElement[data_col="+iCol+"]").addClass("selected");
if(!self.selectedStackElements.includes(iCol)){
self.selectedStackElements.push(iCol);
}
}
}
self.styleStackTable();
};
this.clickRule = function() {
// console.log("select rule")
self.resetFeedback();
self.selectRule($(this));
};
this.clickReductionMarker = function(rule,state,vID) {
return function() {
if(state == self.currentState){
var ruleObj = $("#rules [data_rule="+rule+"]");
self.selectRule(ruleObj);
}else{
var info = self.graph.getVertexInfo(vID);
var raphObj = self.visualGraph.getRaphaelsFromID(vID);
info.selected = !info.selected;
if(info.selected){
raphObj[0].attr(self.defaultSelectedVertexAttr);
}else{
raphObj[0].attr(self.defaultVertexAttr);
}
self.onVertexSelect(vID,info.selected);
// console.log("click")
}
}
}
this.selectRule = function(ruleObj) {
var ruleID = ruleObj.attr("data_rule");
if(ruleObj.hasClass("selected")){
ruleObj.removeClass("selected");
self.selectedRule = null;
self.highlightReductionMarker(ruleID,false);
}else{
$(".rule").removeClass("selected");
for(var data of self.reductionStates){
var state = data.state;
var vID = self.getStateID(state);
var raphObj = self.visualGraph.getRaphaelsFromID(vID);
raphObj[5].attr("fill",self.colors.black);
}
ruleObj.addClass("selected");
self.selectedRule = ruleID;
self.highlightReductionMarker(ruleID,true);
}
self.styleRules();
};
this.unselectRules = function() {
// console.log("uselect rules")
$(".rule").removeClass("selected");
self.selectedRule = null;
self.styleRules();
};
this.highlightReductionMarker = function(rule,selected) {
// console.log(this.currentState)
for(var data of this.reductionStates){
var state = data.state;
if(rule == data.rule){
var vID = this.getStateID(state);
var raphObj = this.visualGraph.getRaphaelsFromID(vID);
if(selected && state == this.currentState){
var color = this.colors.blue;
}else{
var color = this.colors.black;
}
raphObj[5].attr("fill",color);
}
}
};
this.switchTab = function() {
$("#"+self.tabTag[self.selectedTab]).removeClass("selectedTab");
self.selectedTab = 1 - self.selectedTab;
$("#"+self.tabTag[self.selectedTab]).addClass("selectedTab");
self.styleTabSwitch();
self.showTab();
self.updateParseTableHL({action:"switchTab"});
};
this.showTab = function() {
if(this.selectedTab == 1){
$("#"+this.graphPaperID).hide();
$("#"+this.parseTableID).show();
}else{
$("#"+this.graphPaperID).show();
$("#"+this.parseTableID).hide();
}
};
this.getStateID = function(state) {
/* get vertex ID from label*/
if(state == null){
return false
}
var vertices = this.graph.getAllVertices();
for(var vertex of vertices){
var info = this.graph.getVertexInfo(vertex);
if(info.label == state){
return vertex;
}
}
return false;
};
/* REDUCE */
this.reduce = function() {
/* used in mode 2, 6 & 7*/
self.resetFeedback();
if(self.simulationStep < self.actionSequence.length){
self.displayError("You must complete the simulation before performing another action.")
return
}
if(self.mode < 6){
if(self.selectedRule == null){
self.displayMessage("reduce","You must select a rule");
}else if(self.selectedStackElements.length == 0 && self.grammar.rules[self.selectedRule].development[0] != "''"){
self.displayMessage("reduce","You must select a part of the stack");
}else if(!self.compareSelectedRuleAndStack()){
self.displayError("You cannot reduce the selected stack elements with the selected rule");
}else{
var nonTerminal = self.grammar.rules[self.selectedRule].nonterminal;
var previousState = self.getPreviousState();
if(nonTerminal != "S" && !self.lrTable.states[previousState][nonTerminal]){
self.displayError("You cannot reduce with the selected rule from this state");
return
}
var goto = (nonTerminal != "S") ? self.lrTable.states[previousState][nonTerminal][0].actionValue : self.getTerminalState();
self.treeAnim(self.simulationStep,false,true);
self.applyReduction(nonTerminal,goto,true,true);
}
}else{
var selectedIndices = self.getSelectedIndices();
if(self.selectedRule == null){
self.displayError("You must select a rule");
}else if(selectedIndices.length == 0){
self.displayError("You must select at least one symbol");
}else if(!self.compareSelectedRuleAndStack()){
self.displayError("You cannot reduce the selected symbols with the selected rule");
}else{
var rule = self.grammar.rules[self.selectedRule];
var nonTerminal = rule.nonterminal;
var newEntry = {};
var cpt = 0;
var sum = 0;
for(var i of selectedIndices){
newEntry[i] = self.derivationTree[i];
cpt++;
sum += parseInt(i);
delete self.derivationTree[i];
}
var newIndex = Math.round(sum / cpt);
newEntry.nonTerminal = nonTerminal;
self.derivationTree[newIndex] = newEntry;
self.unselectRules();
self.updateTree();
self.initHandlers();
self.saveAnswer();
}
}
};
this.compareSelectedRuleAndStack = function() {
var rule = this.grammar.rules[this.selectedRule];
if(this.mode < 6){
this.selectedStackElements.sort();
if(rule.development[0] == "''" && this.selectedStackElements.length == 0){
return true;
}else if(rule.development.length != this.selectedStackElements.length){
return false;
}else{
for(var iEl = 0; iEl < rule.development.length; iEl++){
if(rule.development[iEl] != this.stack[this.selectedStackElements[iEl]][1]){
return false;
}
}
}
return true;
}else{
var selectedIndices = this.getSelectedIndices();
var selectedSymbols = [];
for(var i of selectedIndices){
selectedSymbols.push(this.treeClickableElements[i].symbol);
}
if(this.mode == 6){
if(Beav.Object.eq(selectedSymbols,rule.development)){
return true;
}
}else if(this.mode == 7){
if(selectedSymbols[0] == rule.nonterminal){
return true;
}
}
return false;
}
};
this.getTerminalState = function() {
// var initVertex = this.getStateID(0);
// if(initVertex){
// var children = this.graph.getChildren(initVertex);
// for(var child of children){
// var edges = this.graph.getEdgesFrom(initVertex,child);
// if(edges.length > 0){
// var edgeInfo = this.graph.getEdgeInfo(edges[0]);
// if(edgeInfo.label == this.grammar.rules[0].nonterminal){
// var vertexInfo = this.graph.getVertexInfo(child);
// if(vertexInfo.terminal){
// return vertexInfo.label;
// }
// }
// }
// }
// }
var vertices = this.graph.getAllVertices();
for(var vertex of vertices){
var info = this.graph.getVertexInfo(vertex);
if(info.terminal){
return info.label
}
}
return null;
};
this.getInitialState = function() {
if(this.mode != 3){
return 0
}else{
var vertices = this.graph.getAllVertices();
for(var vertex of vertices){
var info = this.graph.getVertexInfo(vertex);
if(info.initial){
return info.label;
}
}
}
return null
};
this.getPreviousState = function() {
if(this.selectedStackElements.length > 0){
var previousCol = parseInt(this.selectedStackElements.sort()[0]) - 1;
}else{
var previousCol = this.stack.length - 1;
}
if(this.stack[previousCol]){
return this.stack[previousCol][0];
}else{
return null
}
};
this.applyReduction = function(nonTerminal,goto,anim,firstStepOnly) {
/* firstStepOnly: for manual reduction in two steps (reduction + goto) */
// console.log('applyReduction')
this.clearHighlight();
var newStackElement = [goto,nonTerminal];
this.highlightPrevState(this.currentState);
var prevStates = [this.getPreviousState()];
for(var col of this.selectedStackElements){
prevStates.push(this.stack[col][0]);
}
this.displayExplanation('reduce2', {
popped_states: prevStates.slice(1).join(', '),
non_terminal: nonTerminal,
top_state: prevStates[0],
new_state: goto
});
var state = prevStates.pop();
if(anim){
this.displayMessage("reduce","REDUCE "+this.selectedRule);
var animTime = this.animationTime/prevStates.length;
if(this.mode == 2){
this.disableShiftButton();
this.disableAcceptButton();
}
this.reductionAnimLoop(state,prevStates,animTime,newStackElement,firstStepOnly);
}else{
if(prevStates.length > 0){
this.highlightReductionPath(state,prevStates);
this.highlightReducedStackElements(prevStates);
this.stack.splice(this.selectedStackElements[0],this.selectedStackElements.length,newStackElement);
this.selectedStackElements = [];
}else{
this.stack.push(newStackElement);
}
this.highlightRule(this.selectedRule);
$(".rule").removeClass("selected");
this.selectedRule = null;
this.styleRules();
this.updateStackTable();
this.updateState(anim,"reduction");
this.arrangeEdgeHL();
}
};
this.reductionAnimLoop = function(state,prevStates,animTime,newStackElement,firstStepOnly) {
/* firstStepOnly: for manual reduction in two steps */
if(prevStates.length > 0){
var prevState = prevStates.pop();
}else{
var prevState = state;
}
var selectedRule = self.selectedRule;
var selectedCol = self.selectedStackElements[prevStates.length];
if(selectedCol){
/* highlight */
var stackElementHL = $("<div class=\"stackElementHL\" data_col=\""+selectedCol+"\"></div>");
stackElementHL.css({
position: "absolute",
left: $(".stackElement[data_col="+selectedCol+"]").position().left,
top: 0,
width: $(".stackElement[data_col="+selectedCol+"]").outerWidth(),
height: $("#stackTable").height(),
"background-color": "rgb(0,10,20)",
opacity: "0.1",
border: "1px solid "+this.colors.black
});
this.stackElementsHL.push(stackElementHL);
if(this.mode == 2){
stackElementHL.off("click");
stackElementHL.click(self.selectStackElement);
}
$("#stackTableContainer").append(stackElementHL);
$(".stackElement[data_col="+selectedCol+"]").fadeOut(animTime);
}
var reduc = true;
if(prevStates.length == 0){
this.currentState = prevState;
// console.log(state+" "+newStackElement)
this.updateParseTableHL({action:"reduction",anim:true,newStackElement:newStackElement});
if(firstStepOnly){
reduc = false;
this.waitingForGoto = true;
}
}
this.changeStateAnim(state,prevState,animTime,reduc || self.waitingForGoto,function(){
self.displayMessage("reduce","REDUCE "+selectedRule);
if(prevStates.length > 0){
self.reductionAnimLoop(prevState,prevStates,animTime,newStackElement,firstStepOnly);
}else{
if(self.selectedStackElements.length > 0){
self.stack.splice(self.selectedStackElements[0],self.selectedStackElements.length,newStackElement);
self.selectedStackElements = [];
}else{
self.stack.push(newStackElement);
selectedCol = self.stack.length - 1;
}
// console.log(newStackElement)
self.updateStackTable(firstStepOnly);
$(".stackElement[data_col="+selectedCol+"]").hide();
$(".stackElement[data_col="+selectedCol+"]").fadeIn(self.animationTime,function(){
if(!$(this).hasClass("State")){
/* prevent double callback */
return
}
if(!firstStepOnly){
self.goto(newStackElement[0]);
self.arrangeEdgeHL();
}else{
$("#reduceButton span").text("GOTO");
$("#reduceButton").off("click");
$("#reduceButton").click(self.clickGoto(newStackElement[0],selectedRule));
}
});
$(".stackElement.State[data_col="+selectedCol+"]").text("");
}
});
};
this.clickGoto = function(goto,rule) {
return function() {
if(self.selectedState == null){
self.displayMessage("reduce","You must select a state in the automaton");
}else if(self.selectedState != goto){
self.displayError("Wrong goto state");
}else{
self.actionSequence.push({
actionType: "r",
rule: rule,
goto: goto
});
self.simulationStep++;
self.currentState = goto;
self.goto(goto);
self.saveAnswer();
$("#reduceButton span").text("REDUCE");
$("#reduceButton").off("click");
$("#reduceButton").click(self.reduce);
}
}
};
this.goto = function(newState) {
// console.log("goto "+newState)
var col = this.stack.length - 1;
$(".stackElement.State[data_col="+col+"]").text(newState);
$(".stackElement.State[data_col="+col+"]").css("opacity",0);
$(".stackElement.State[data_col="+col+"]").animate({opacity:1},self.animationTime);
self.highlightRule(self.selectedRule);
self.updateState(true,"goto");
self.displayMessage("reduce","GOTO "+newState);
self.currentVertex = self.getStateID(newState);
self.arrangeEdgeHL();
if(this.mode == 2){
self.enableShiftButton();
self.enableAcceptButton();
}
};
this.reverseReduction = function(rule) {
var newStackElements = [];
this.stack.pop();
var nonTerminal = this.grammar.rules[rule].nonterminal;
var development = this.grammar.rules[rule].development;
var previousState = this.stack[this.stack.length - 1][0];
for(var symbol of development){
var state = this.lrTable.states[previousState][symbol][0].actionValue;
this.stack.push([state,symbol]);
previousState = state;
}
this.updateStackTable();
this.styleStackTable();
this.updateState(false,"reverseReduction");
};
/* SHIFT */
this.shift = function() {
self.resetFeedback();
if(self.simulationStep < self.actionSequence.length){
self.displayError("You must complete the simulation before performing another action.")
return
}
if(self.selectedState == null){
var error = "You must select a state in the automaton";
self.displayMessage("shift",error);
}else{
var edge = self.graph.getEdgesFrom(self.currentVertex,self.selectedVertex);
if(edge.length == 0){
self.displayError("This action is not allowed");
}else{
var edgeInfo = self.graph.getEdgeInfo(edge[0]);
var action = self.lrTable.states[self.currentState][edgeInfo.label][0];
if(!action){
self.displayError("This action is not allowed");
}else if(edgeInfo.label != self.input.charAt(self.inputIndex)){
self.displayError("Invalid shift");
}else{
self.actionSequence.push({
actionType: "s",
state: self.selectedState
});
self.simulationStep++;
self.applyShift(self.selectedState,false,true);
self.saveAnswer();
}
}
}
};
this.applyShift = function(newState,reverse,anim) {
this.displayExplanation('shift', {
symbol: this.input[this.inputIndex],
state: newState
});
this.clearHighlight();
if(reverse){
this.stack.pop();
this.inputIndex--;
newState = this.stack[this.stack.length - 1][0];
}else{
this.stack.push([newState,this.input[this.inputIndex]]);
this.inputIndex++;
}
this.currentVertex = this.getStateID(newState);
if(!reverse){
this.highlightPrevState(this.currentState);
}
this.selectedStackElements = [];
this.updateStackTable();
this.updateCursor(!(reverse || !anim));
this.updateState(!(reverse || !anim),"shift");
// console.log(this.selectedState)
};
this.updateStackTable = function(noGoto) {
var html = "";
for(var iData = 0; iData < this.stackData.length; iData++) {
html += "<tr>";
for(var iElement = 0; iElement < this.stack.length; iElement++){
html += "<td class=\""+this.stackData[iData]+" stackElement"+((this.selectedStackElements.includes(""+iElement)) ? " selected" : "")+"\" data_col="+iElement+">"+this.stack[iElement][iData]+"</td>";
}
html += "<td>"+this.stackData[iData]+"</td>";
html += "</tr>";
}
$("#stackTable").html(html);
this.styleStackTable();
if(this.mode == 2){
$(".stackElement").off("click");
$(".stackElement").click(self.selectStackElement);
}
if(!noGoto){ // reduc in mode 5
this.currentState = this.stack[this.stack.length - 1][0];
this.currentVertex = this.getStateID(this.currentState);
}
// console.log("updateStack "+this.currentState);
};
this.updateCursor = function(anim) {
var newX = (this.inputIndex == 0) ? 0 : (this.inputIndex + 1/2) * $(".inputChar").outerWidth();
var xHL = $("#cursor").position().left;
var wHL = newX - xHL;
if(this.inputHighlight){
this.inputHighlight.remove();
}
this.inputHighlight = $("<div id=\"inputHL\"></div>");
this.inputHighlight.css({
position: "absolute",
left: xHL,
top: 0,
height: "100%",
width: wHL+"px",
"background-color": "rgb(0,10,20)",
opacity: "0.1"
});
$("#inputBar").append(this.inputHighlight);
if(anim){
$("#cursor, #treeCursor").animate({left:newX+"px"},this.animationTime,function() {
self.updateInputColor();
});
}else{
$("#cursor, #treeCursor").css({left:newX+"px"});
this.updateInputColor();
}
};
this.updateInputColor = function() {
$("#inputBar .inputChar").each(function(index) {
if(index < self.inputIndex){
$(this).addClass("read");
}else{
$(this).removeClass("read");
}
});
self.styleInput();
};
/* HIGHLIGHT */
this.highlightPrevState = function(previousState) {
var vertex = this.getStateID(previousState);
var raphObj = this.visualGraph.getRaphaelsFromID(vertex);
// var x = raphObj[0].attr("x");
// var y = raphObj[0].attr("y");
var pos = this.visualGraph.getVertexVisualInfo(vertex);
var width = raphObj[0].attr("width");
var height = raphObj[0].attr("height");
var r = raphObj[0].attr("r");
this.prevStateHighlight = this.paper.rect(pos.x - width/2,pos.y - height/2,width,height,r).attr(this.previousStateAttr)
};
this.highlightEdge = function(edgeID,back) {
if(!edgeID){
return;
}
// console.log("HL edge "+edgeID+" "+back)
var attr = this.edgeHighlightAttr;
var raphObj = this.visualGraph.getRaphaelsFromID(edgeID);
var edgeHL = raphObj[0].clone();
if(back){
edgeHL.attr(attr.bwd);
}else{
edgeHL.attr(attr.fwd);
}
edgeHL.toBack();
raphObj[1].toBack();
raphObj[0].toBack();
var type = (back) ? "bwd" : "fwd";
if(!this.pathHighlight[edgeID]){
this.pathHighlight[edgeID] = {};
}else if(this.pathHighlight[edgeID][type]){
this.pathHighlight[edgeID][type].remove();
}
this.pathHighlight[edgeID][type] = edgeHL;
};
this.highlightReductionPath = function(state,prevStates) {
var prevStates = JSON.parse(JSON.stringify(prevStates));
do{
var vertexID = this.getStateID(state);
var prevState = prevStates.pop();
var prevVertexID = this.getStateID(prevState);
var edgeID = this.graph.getEdgesBetween(prevVertexID,vertexID)[0];
this.highlightEdge(edgeID,true);
state = prevState;
}while(prevStates.length > 0)
};
this.arrangeEdgeHL = function() {
/* put fwd HL in front of bwd HL */
for(var edgeID in this.pathHighlight){
var raphObj = this.visualGraph.getRaphaelsFromID(edgeID);
var elem = this.pathHighlight[edgeID];
if(elem["bwd"] && elem["fwd"]){
elem["fwd"].attr("stroke-dasharray","- ").toBack();
elem["bwd"].toBack();
}
raphObj[1].toBack();
raphObj[0].toBack();
}
};
this.highlightRule = function(rule) {
$(".rule").removeClass("selected");
$(".rule[data_rule="+rule+"]").addClass("previousRule");
self.selectedRule = null;
self.styleRules();
self.highlightReductionMarker(rule,false);
};
this.highlightReducedStackElements = function(prevStates) {
var prevStates = JSON.parse(JSON.stringify(prevStates));
do{
var prevState = prevStates.pop();
var selectedCol = self.selectedStackElements[prevStates.length];
var stackElementHL = $("<div class=\"stackElementHL\" data_col=\""+selectedCol+"\"></div>");
stackElementHL.css({
position: "absolute",
left: $(".stackElement[data_col="+selectedCol+"]").position().left,
top: 0,
width: $(".stackElement[data_col="+selectedCol+"]").outerWidth(),
height: $("#stackTable").height(),
"background-color": "rgb(0,10,20)",
opacity: "0.1",
border: "1px solid "+this.colors.black
});
this.stackElementsHL.push(stackElementHL);
if(this.mode == 2){
stackElementHL.off("click");
stackElementHL.click(self.selectStackElement);
}
$("#stackTableContainer").append(stackElementHL);
}while(prevStates.length > 0)
};
this.clearHighlight = function() {
// console.log("clearHL");
if(this.inputHighlight){
this.inputHighlight.remove();
this.inputHighlight = null;
}
if(this.prevStateHighlight){
this.prevStateHighlight.remove();
this.prevStateHighlight = null;
}
for(var edge in this.pathHighlight){
var edgeHL = this.pathHighlight[edge];
for(var dir in edgeHL){
if(edgeHL[dir]){
edgeHL[dir].remove();
delete edgeHL[dir];
}
}
}
if(this.stackElementsHL.length > 0){
for(var elem of this.stackElementsHL){
elem.remove();
}
this.stackElementsHL = [];
}
$(".rule").removeClass("previousRule");
this.styleRules();
};
this.resetParseTableHL = function() {
// console.log("reset")
this.styleParseTable();
if(this.gotoColHL){
self.gotoColHL.remove();
self.gotoColHL = null;
self.removeGotoColHL = false;
}
};
this.updateParseTableHL = function(data) {
if(this.mode == 3){
if($("#"+this.parseTableID+" td[data_state=\""+this.currentState+"\"]").length == 0){
if(this.rowHL){
this.rowHL.remove();
this.rowHL = null;
}
if(this.colHL){
this.colHL.remove();
this.colHL = null;
}
this.updateStackPreview();
return
}
}
if(data){
var anim = data.anim;
var newStackElement = data.newStackElement;
var nonTerminal = data.nonTerminal;
var action = data.action;
}
// console.log("updateParseTable "+action+" "+anim);
if(!this.rowHL && this.mode != 4){
this.rowHL = $("<div id=\"rowHL\"></div>");
$("#"+this.parseTableID).append(this.rowHL);
this.rowHL.css(this.cellHighlightAttr);
}
if(!this.colHL && this.mode != 4){
this.colHL = $("<div id=\"colHL\"></div>");
$("#"+this.parseTableID).append(this.colHL);
this.colHL.css(this.cellHighlightAttr);
}
var tableW = $("#"+this.parseTableID+" table").width();
var tableH = $("#"+this.parseTableID+" table").height();
var tablePos = $("#"+this.parseTableID+" table").position();
var tableMarginLeft = ($("#"+this.parseTableID).width() - tableW)/2;
var actionH = $("#"+this.parseTableID+" table th:nth-child(2)").outerHeight();
var rowH = $("#"+this.parseTableID+" td[data_state=\""+this.currentState+"\"]").outerHeight();
var colW = $("#"+this.parseTableID+" td[data_symbol=\""+this.input[this.inputIndex]+"\"]").outerWidth();
var rowTop = 0;
if($("#"+this.parseTableID+" td[data_state=\""+this.currentState+"\"]").position()){
rowTop = $("#"+this.parseTableID+" td[data_state=\""+this.currentState+"\"]").position().top;
}
var colLeft = 0;
if($("#"+this.parseTableID+" td[data_symbol=\""+this.input[this.inputIndex]+"\"]").position()){
var colLeft = $("#"+this.parseTableID+" td[data_symbol=\""+this.input[this.inputIndex]+"\"]").position().left;
}
if(action == "startReduction" && anim){
/* column selector change color + goto column selector appears */
this.updateStackPreview(data.rule,data.goto);
if(this.removeGotoColHL){
this.removeGotoColHL = false;
this.gotoColHL.remove();
this.styleParseTable();
}
this.colHL.css("border-color",this.colors.lightBlue);
this.gotoColHL = $("<div id=\"gotoColHL\"></div>");
$("#"+this.parseTableID).append(this.gotoColHL);
this.gotoColHL.css(this.cellHighlightAttr);
var gotoColW = $("#"+this.parseTableID+" td[data_symbol=\""+nonTerminal+"\"]").outerWidth();
var gotoColLeft = $("#"+this.parseTableID+" td[data_symbol=\""+nonTerminal+"\"]").position().left;
var gotoColAttr = {
width: gotoColW - 4,
height: tableH - 4 - actionH,
top: actionH,
left: gotoColLeft - 2
};
this.gotoColHL.css(gotoColAttr);
this.gotoColHL.hide();
this.gotoColHL.fadeIn(this.animationTime);
return
}
var newRowAttr = {
width: tableW - 4,
height: rowH - 4,
top: rowTop - 2,
left: tableMarginLeft - 2
};
var newColAttr = {
width: colW - 4,
height: tableH - 4 - actionH,
top: actionH,
left: colLeft - 2
};
if(this.mode != 4 && this.mode != 2){
if(!anim){
this.rowHL.css(newRowAttr);
this.colHL.css(newColAttr);
}else{
var tempState = this.currentState;
if(action == "goto"){
this.removeGotoColHL = true;
this.gotoColHL.fadeOut(this.animationTime);
}
this.rowHL.animate(newRowAttr,this.animationTime,function(){
// console.log("end anim "+action);
if(newStackElement){
$("#parseTable [data_state="+tempState+"][data_symbol="+newStackElement[1]+"]").css({
"background-color": self.colors.blue,
color: "white" });
}else if(self.gotoColHL && action == "goto" && self.removeGotoColHL){
// console.log("remove goto")
self.gotoColHL.remove();
self.gotoColHL = null;
self.styleParseTable();
self.removeGotoColHL = false;
}
});
this.colHL.animate(newColAttr,this.animationTime);
}
}
// console.log(this.inputIndex+" "+this.input)
if(!newStackElement){
this.updateStackPreview();
}
};
this.updateStackPreview = function(rule,goto) {
var attr = this.stackPreviewAttr;
var localStack = JSON.parse(JSON.stringify(this.stack));
if(rule || rule == 0){
this.stackPreviewElements.circle.attr(attr.previousCircle);
this.stackPreviewElements.line.attr(attr.previousLine);
this.stackPreviewElements.letterCircle.attr(attr.previousSymbolCircle);
this.stackPreviewElements.letter.attr(attr.previousSymbol);
var ruleData = this.grammar.rules[rule];
var dev = ruleData.development;
var nonTerminal = ruleData.nonterminal;
var newStackElement = [goto,nonTerminal];
var index = localStack.length - dev.length;
localStack.splice(index,dev.length,newStackElement);
}else{
this.stackPreview.clear();
}
var tableW = $("#"+this.parseTableID+" table").width();
var tableH = $("#"+this.parseTableID+" table").height();
var tablePos = $("#"+this.parseTableID+" table").position();
var tableMarginLeft = ($("#"+this.parseTableID).width() - tableW)/2;
var headerH = $("#"+this.parseTableID+" table th:first-child").outerHeight();
stackPreviewH = tableH + 4;
stackPreviewW = Math.max(localStack.length,this.stack.length)*attr.colW;
this.stackPreview.setSize(stackPreviewW,stackPreviewH);
$("#stackPreview").css({
position: "absolute",
top: 0,
left: tableMarginLeft - stackPreviewW - 2
});
this.stackPreview.rect(0,headerH,stackPreviewW,stackPreviewH - headerH).attr(attr.background).toBack();
var lastPos = null;
var x = stackPreviewW/2;
// this.stackPreview.setStart();
this.stackPreviewElements = {circle:this.stackPreview.set(),line:this.stackPreview.set(),letterCircle:this.stackPreview.set(),letter:this.stackPreview.set()};
for(var iElem = 0; iElem < localStack.length; iElem++){
var elem = localStack[iElem];
var state = elem[0];
var symbol = elem[1];
var line = $("#"+this.parseTableID+" td[data_state=\""+state+"\"]");
if(line.length > 0){
var x = (iElem + 1/2)*attr.colW;
var y = line.position().top + line.outerHeight()/2;
var circle = this.stackPreview.circle(x,y).attr(attr.circle);
this.stackPreviewElements.circle.push(circle);
if(lastPos){
var line = this.stackPreview.path("M"+lastPos.x+" "+lastPos.y+",L"+x+" "+y).attr(attr.line);
var xSymbol = (x + lastPos.x)/2;
var ySymbol = (y + lastPos.y)/2;
var letterCircle = this.stackPreview.circle(xSymbol,ySymbol).attr(attr.symbolCircle);
var letter = this.stackPreview.text(xSymbol,ySymbol,symbol).attr(attr.symbol);
this.stackPreviewElements.line.push(line);
this.stackPreviewElements.letterCircle.push(letterCircle);
this.stackPreviewElements.letter.push(letter);
}
lastPos = { x: x, y: y };
}
}
// this.stackElements = this.stackPreview.setFinish();
};
/* UPDATE PARSE TABLE (mode 3) */
this.updateParseTable = function() {
// console.log("update Parse table");
// console.log(this.grammar);
this.clearParseTable();
this.computedStates = {};
var vertices = this.graph.getAllVertices();
for(var vertex of vertices){
this.addLineToParseTable(vertex);
}
for(var vertex of vertices){
var info = this.graph.getVertexInfo(vertex);
this.computedStates[info.label] = {};
var children = this.graph.getChildren(vertex);
if(children.length > 0){
for(var child of children){
var gotoOrShift = this.gotoOrShift(vertex,child);
if(gotoOrShift){
var childInfo = this.graph.getVertexInfo(child);
this.addGotoOrShift(gotoOrShift,info.label,childInfo.label);
var action = (!this.grammar.terminals.includes(gotoOrShift)) ? "goto" : "shift";
this.addComputedState({
state1: info.label,
symbol: gotoOrShift,
state2: childInfo.label,
action: action
});
if(childInfo.terminal && gotoOrShift == this.grammar.axiom){
this.addAccept(childInfo.label);
}
}
}
}
var rule = this.findReduction(vertex);
if(rule !== false){
var nonTerminal = this.grammar.rules[rule].nonterminal;
var follows = this.grammar.follows[nonTerminal];
this.addReduction(rule,follows,info.label);
this.addComputedState({
state1: info.label,
rule: rule,
follows: follows,
action: "reduction"
});
}
}
this.styleParseTable();
// this.updateStackPreview();
this.updateParseTableHL();
};
this.clearParseTable = function() {
$("#parseTable table tr").each(function(index) {
if(index > 1){
$(this).remove();
}
});
};
this.addLineToParseTable = function(vertex) {
var vInfo = this.graph.getVertexInfo(vertex);
var state = vInfo.label;
var html = "<tr><td data_state="+state+">"+state+"</td>";
$("#parseTable table tr:nth-child(2) th").each(function(index) {
var symbol = $(this).text();
html += "<td data_state="+state+" data_symbol="+symbol+"></td>";
});
html += "</tr>";
$("#parseTable table").append(html);
};
this.gotoOrShift = function(vertex,child) {
var edges = this.graph.getEdgesFrom(vertex,child);
var edge = edges[0];
var edgeInfo = this.graph.getEdgeInfo(edge);
var edgeLabel = edgeInfo.label;
if(edgeLabel.length == 1){
var vertexContent = this.readContent(vertex);
if(!vertexContent){
return false
}
var childContent = this.readContent(child);
if(!childContent){
return false
}
for(var vertexLine of vertexContent){
for(var childLine of childContent){
if(vertexLine.nonTerminal == childLine.nonTerminal && vertexLine.development.length == childLine.development.length){
var match = true;
for(var symbol of vertexLine.development){
if(!childLine.development.includes(symbol)){
match = false;
break;
}
}
var vertexDot = vertexLine.development.indexOf(dot);
var childDot = childLine.development.indexOf(dot);
if(match && vertexDot == childDot - 1
&& vertexLine.development[childDot] == edgeLabel
&& childLine.development[vertexDot] == edgeLabel){
return edgeLabel
}
}
}
}
}
return false
};
this.findReduction = function(vertex) {
var vertexContent = this.readContent(vertex);
if(vertexContent.length > 0){
for(var vertexLine of vertexContent){
if(vertexLine.development[vertexLine.development.length - 1] == dot){
var rule = this.findRule(vertexLine);
// console.log("rule:"+rule+" state:"+this.graph.getVertexInfo(vertex).label);
if(rule !== false){
var ruleData = this.grammar.rules[rule];
if(ruleData.development.length == 1 && ruleData.development[0] == "''"){
return rule
}
var parents = this.graph.getParents(vertex);
for(var parent of parents){
var findReductionPath = this.findReductionPath(vertex,parent,vertexLine,rule);
if(findReductionPath){
return rule;
}
}
}
}
}
}
return false
};
this.findRule = function(line) {
for(var rule of this.grammar.rules){
if(line.nonTerminal == rule.nonterminal){
var dev = JSON.parse(JSON.stringify(line.development));
var dotIndex = dev.indexOf(dot);
dev.splice(dotIndex,1);
var match = true;
if(rule.development.length == 1 && rule.development[0] == "''" && dev.length == 0){
return rule.index
}
if(dev.length != rule.development.length){
match = false;
}else{
for(var iSymbol = 0; iSymbol < rule.development.length; iSymbol++){
if(rule.development[iSymbol] != dev[iSymbol]){
match = false;
break;
}
}
}
if(match){
return rule.index;
}
}
}
return false;
};
this.findReductionPath = function(vertex,parent,vertexLine,rule) {
var edges = this.graph.getEdgesFrom(parent,vertex);
var edge = edges[0];
var edgeInfo = this.graph.getEdgeInfo(edge);
var edgeLabel = edgeInfo.label;
if(edgeLabel.length != 1){
return false
}else{
var parentContent = this.readContent(parent);
for(var parentLine of parentContent){
if(vertexLine.nonTerminal == parentLine.nonTerminal && vertexLine.development.length == parentLine.development.length){
var match = true;
var dotIndex = parentLine.development.indexOf(dot);
if(dotIndex != vertexLine.development.indexOf(dot) - 1){
match = false;
}else{
var dev = JSON.parse(JSON.stringify(parentLine.development));
dev.splice(dotIndex,1);
var ruleData = this.grammar.rules[rule];
for(var iSymbol = 0; iSymbol < ruleData.development.length; iSymbol++){
if(ruleData.development[iSymbol] != dev[iSymbol]){
match = false;
break;
}
}
}
if(match){
if(dotIndex == 0){
return true;
}else{
var grandparents = this.graph.getParents(parent);
for(var grandparent of grandparents){
var findPath = this.findReductionPath(parent,grandparent,parentLine,rule);
if(findPath){
return findPath;
}
}
}
}
}
}
}
return false;
};
this.addGotoOrShift = function(symbol,state1,state2) {
if(this.grammar.terminals.includes(symbol)){
var text = "s"+state2;
}else{
var text = state2;
}
$("#parseTable [data_state="+state1+"][data_symbol=\""+symbol+"\"]").text(text);
};
this.addReduction = function(rule,follows,state) {
var displayedRule = rule + 1;
var html = "<span class=\"ruleMarker\">r<span class=\"ruleMarkerIndex\">"+displayedRule+"</span></span>";
for(var follow of follows){
$("#parseTable [data_state="+state+"][data_symbol=\""+follow+"\"]").html(html);
}
};
this.addAccept = function(state) {
var html = "<span class=\"ruleMarker\"><span class=\"ruleMarkerIndex\">acc.</span></span>";
$("#parseTable [data_state="+state+"][data_symbol=\"$\"]").html(html);
};
this.addComputedState = function(data) {
switch(data.action){
case "shift":
var actionType = "s";
var actionValue = data.state2;
break;
case "goto":
var actionType = "";
var actionValue = data.state2;
break;
case "reduction":
var actionType = "r";
var actionValue = data.rule;
}
if(data.action != "reduction"){
this.computedStates[data.state1][data.symbol] = [{
actionType: actionType,
actionValue: actionValue
}];
}else{
for(var follow of data.follows){
this.computedStates[data.state1][follow] = [{
actionType: actionType,
actionValue: actionValue
}];
}
}
};
/**/
this.updateState = function(anim,action) {
var id = this.getStateID(this.currentState);
// console.log("updateState "+anim)
this.updateParseTableHL({anim:anim,action:action});
this.resetStates();
if(!id){
return;
}
var previousState = (this.stack.length > 1) ? this.stack[this.stack.length - 2][0] : null;
if(!anim){
this.styleVertex(id,"current");
if(previousState != null){
var id1 = this.getStateID(previousState);
var edgeID = this.graph.getEdgesBetween(id1,id)[0];
this.highlightEdge(edgeID);
}
}else{
// var previousState = this.stack[this.stack.length - 2][0];
// var animTime = (action == "shift") ? this.animationTime*0.8 : this.animationTime;
this.changeStateAnim(previousState,this.currentState,this.animationTime);
}
if(this.currentState == this.getTerminalState()){
if(!this.accept)
this.acceptInput();
}
};
this.changeStateAnim = function(state1,state2,time,reduction,callback) {
var id2 = this.getStateID(state2);
var id1 = this.getStateID(state1);
var vInfo1 = this.visualGraph.getVertexVisualInfo(id1);
var vInfo2 = this.visualGraph.getVertexVisualInfo(id2);
var edgeID = this.graph.getEdgesBetween(id1,id2)[0];
var edgeVisualInfo = this.visualGraph.getEdgeVisualInfo(edgeID);
var initPos = vInfo1;
var finalPos = vInfo2;
this.highlightEdge(edgeID,reduction);
if(state1 == state2){
if(this.token){
this.token.remove();
}
if(callback){
callback();
}
return
}else if(!edgeVisualInfo["radius-ratio"]){
/* straight line */
var alpha = this.visualGraph.graphDrawer.getAngleBetween(vInfo1.x,vInfo1.y,vInfo2.x,vInfo2.y);
var info1 = this.graph.getVertexInfo(id1);
var info2 = this.graph.getVertexInfo(id2);
var content1 = (info1.content) ? info1.content : "";
var content2 = (info2.content) ? info2.content : "";
var boxSize1 = this.visualGraph.graphDrawer.getBoxSize(content1);
var boxSize2 = this.visualGraph.graphDrawer.getBoxSize(content2);
var pos1 = this.visualGraph.graphDrawer.getSurfacePointFromAngle(vInfo1.x,vInfo1.y,boxSize1.w,boxSize1.h,alpha + Math.PI);
var pos2 = this.visualGraph.graphDrawer.getSurfacePointFromAngle(vInfo2.x,vInfo2.y,boxSize2.w,boxSize2.h,alpha);
var step1 = {transform: "t"+(vInfo2.x - vInfo1.x)+","+(vInfo2.y - vInfo1.y)};
}else{
/* curve */
var param = this.visualGraph.graphDrawer.getEdgeParam(edgeID);
var cPos = this.visualGraph.graphDrawer.getCenterPosition(param.R,param.s,param.l,param.pos1,param.pos2);
var alpha = (param.l) ? (Math.asin(param.D/(2*param.R)) + Math.PI) : Math.asin(param.D/(2*param.R));
var alpha = -2*alpha*180/Math.PI;
if(this.graph.getEdgesFrom(id1,id2).length > 0 && this.graph.getEdgesFrom(id1,id2)[0] == edgeID){
var pos1 = param.pos1;
var pos2 = param.pos2;
var angle = (param.s) ? -alpha : alpha;
}else{
var pos1 = param.pos2;
var pos2 = param.pos1;
var angle = (param.s) ? alpha : -alpha;
}
var distance1 = Math.sqrt((pos1.x - initPos.x) * (pos1.x - initPos.x) + (pos1.y - initPos.y) * (pos1.y - initPos.y));
var distance2 = Math.abs(param.R * alpha * Math.PI/180);
var distance3 = Math.sqrt((finalPos.x - pos2.x) * (finalPos.x - pos2.x) + (finalPos.y - pos2.y) * (finalPos.y - pos2.y));
var totalDistance = distance1 + distance2 + distance3;
var time1 = time * distance1 / totalDistance;
var time2 = time * distance2 / totalDistance;
var time3 = time * distance3 / totalDistance;
var step1 = {transform: "t"+(pos1.x - vInfo1.x)+","+(pos1.y - vInfo1.y)};
var step2 = {transform: "r "+angle+","+(cPos.x)+","+(cPos.y)};
var step3 = {transform: "t"+(vInfo2.x - pos2.x)+","+(vInfo2.y - pos2.y)};
}
self.styleVertex(id1,"default");
if(this.token){
this.token.remove();
}
this.token = this.paper.circle(initPos.x,initPos.y,10).attr({"fill":this.colors.blue,"stroke": "none"});
if(!edgeVisualInfo["radius-ratio"]){
var anim1 = new Raphael.animation(step1,time,function(){
self.resetStates();
if(!reduction){
self.styleVertex(id2,"current");
self.token.remove();
}
self.displayMessage("reset");
if(callback)
callback();
});
}else{
var anim1 = new Raphael.animation(step1,time1,function(){
self.token.transform("");
self.token.attr({cx:pos1.x,cy:pos1.y});
subTask.raphaelFactory.animate("anim",self.token,anim2);
});
var anim2 = new Raphael.animation(step2,time2,function(){
self.token.transform("");
self.token.attr({cx:pos2.x,cy:pos2.y});
subTask.raphaelFactory.animate("anim",self.token,anim3);
});
var anim3 = new Raphael.animation(step3,time3,function(){
self.resetStates();
if(!reduction){
self.styleVertex(id2,"current");
self.token.remove();
}
self.displayMessage("reset");
if(callback)
callback();
});
}
subTask.raphaelFactory.animate("anim",this.token,anim1);
};
this.resetStates = function() {
var vertices = this.graph.getAllVertices();
for(var vertexID of vertices){
var info = this.graph.getVertexInfo(vertexID);
this.styleVertex(vertexID,"default");
info.selected = false;
this.graph.setVertexInfo(vertexID,info);
}
this.selectedState = null;
if(this.mode == 2){
this.graphEditor.vertexDragAndConnect.selectionParent = null;
}
};
/* ACCEPT / ERROR */
this.acceptInput = function() {
self.resetFeedback();
self.accept = !self.accept;
if(self.accept){
$("#acceptButton").css({
"background-color": self.colors.yellow
});
$("#acceptMessage").parent().css({
"background-color": self.colors.yellow
});
if(self.error){
self.refuseInput();
} else {
this.displayExplanation('accepted', {
state: this.currentState,
base_reduction_item: this.stack[this.stack.length - 1][1]
});
}
}else{
$("#acceptButton").css({
"background-color": self.colors.blue
});
$("#acceptMessage").parent().css({
"background-color": self.colors.blue
});
}
self.saveAnswer();
};
this.refuseInput = function() {
self.resetFeedback();
self.error = !self.error;
if(self.error){
$("#errorButton").css({
"background-color": self.colors.yellow
});
$("#errorMessage").parent().css({
"background-color": self.colors.yellow
});
if(self.accept){
self.acceptInput();
} else {
this.displayExplanation('error', {
symbol: this.input[this.inputIndex],
state: this.currentState
});
}
}else{
$("#errorButton").css({
"background-color": self.colors.blue
});
$("#errorMessage").parent().css({
"background-color": self.colors.blue
});
this.displayExplanation('not_accepted');
}
self.saveAnswer();
};
/** AUTOMATON **/
this.doesAutomatonAllowAction = function(state,symbol,action){
// console.log(state);
var v1 = this.getStateID(state);
switch(action[0].actionType){
case "s":
case "":
var v2 = this.getStateID(action[0].actionValue);
if(v1 === false || v2 === false){
return false
}
var edge = this.graph.getEdgesFrom(v1,v2);
if(edge.length == 0){
return false
}
var edgeLabel = this.graph.getEdgeInfo(edge).label;
if(edgeLabel != symbol){
return false
}
break;
case "r":
if(!this.contentAllowReduction(v1,action[0].actionValue)){
return false;
}
break;
default:
return false;
}
return true;
};
this.contentAllowReduction = function(id,rule) {
var content = this.readContent(id);
if(content){
var ruleArray = this.grammar.rules[rule];
for(var line of content){
// console.log(line);
var match = true;
if(line.nonTerminal != ruleArray.nonterminal){
match = false;
}else if(ruleArray.development[0] != "''" && line.development.length != ruleArray.development.length + 1){
match = false;
}else if(ruleArray.development[0] == "''"){
if(line.development.length != 1 || line.development[0] != dot){
match = false;
}
}else{
for(var iChar in line.development){
if(iChar == (line.development.length - 1) && line.development[iChar] != dot){
match = false;
}else if(iChar < (line.development.length - 1) && line.development[iChar] != ruleArray.development[iChar]){
match = false;
}
}
}
if(match == true){
return true
}
}
}
return false
};
this.readContent = function(id) {
/* return the vertex content as an array of objects {nonTerminal,[development]}*/
var contentObj = [];
var info = this.graph.getVertexInfo(id);
if(!info || !info.content){
return false;
}
var lines = info.content.split('\n');
for(var line of lines){
var rule = line.split(arrow);
if(rule.length <= 1){
return false
}
var nonTerminal = rule[0];
var development = rule[1].trim();
contentObj.push({
nonTerminal: nonTerminal.trim(),
development: development.split(' ')
});
}
// console.log(contentObj);
return contentObj;
};
this.readLine = function(line) {
/* return the rule and dot index (validation) */
var ruleIndex = null;
var developmentStr = line.development.join("");
var dotIndex = developmentStr.indexOf(dot);
var lastDotIndex = developmentStr.lastIndexOf(dot);
if(lastDotIndex != dotIndex){
dotIndex = null;
}
var developmentNoDot = line.development.filter(x => x != dot);
for(var rule of this.grammar.rules){
if(rule.nonterminal == line.nonTerminal && (Beav.Object.eq(developmentNoDot,rule.development) || (rule.development[0] == "''" && developmentNoDot.length == 0))){
ruleIndex = rule.index;
}
}
return {ruleIndex: ruleIndex, dotIndex: dotIndex};
};
this.graphEditorCallback = function() {
// console.log("graphEditorCallback ")
self.clearHighlight();
self.pauseSimulation(null,true);
self.resetFeedback();
self.actionSequence = [];
self.reset();
// self.formatContent();
// self.graphEditor.resizeTableVertex(vertex,info.content);
// self.initActionSequence();
// self.updateStackTable();
self.saveAnswer();
self.isLastActionAGraphEdit = true;
self.updateParseTable();
self.derivationTree = [];
self.initActionSequence(false,true);
self.styleDerivationTree();
self.showUndo();
// console.log(self.computedStates);
if(answer.visualGraphJSON.length > 1){
self.enableUndoButton()
}
};
this.selectVertexCallback = function(id,selected) {
// console.log(id+" "+selected)
var current = self.getStateID(self.currentState);
if(!selected && id == current){
self.styleVertex(id,"current");
}
if(selected){
self.styleVertex(id,"selected");
}
};
this.onVertexSelect = function(ID,selected) {
// console.log("onVertexSelect"+" "+ID+" "+selected)
var info = self.graph.getVertexInfo(ID);
var stateVertex = self.visualGraph.getRaphaelsFromID(ID);
if(selected){
self.selectedVertex = ID;
self.selectedState = info.label;
stateVertex[4].attr(self.defaultSelectedVertexAttr);
}else{
self.selectedVertex = null;
self.selectedState = null;
stateVertex[4].attr(self.headerAttr);
if(self.prevStateHighlight){
self.prevStateHighlight.toFront();
}
}
self.resetFeedback();
if(ID == self.getStateID(self.currentState)){
self.styleVertex(ID,"current");
if(selected){
self.styleVertex(ID,"selected");
}
}
if(self.reductionClickArea[ID]){
self.reductionClickArea[ID].toFront();
}
};
this.formatContent = function() {
var vertices = this.graph.getAllVertices();
for(var vertex of vertices){
this.formatVertexContent(vertex);
}
};
this.formatVertexContent = function(vertex) {
var info = this.graph.getVertexInfo(vertex);
if(info.content){
info.content = info.content.replace(/->/g,arrow);
info.content = info.content.replace(/\./g,dot);
info.content = info.content.replace(/ /g,"");
var lines = info.content.split('\n');
var formatedContent = "";
for(var iLine in lines){
var line = lines[iLine];
var formatedLine = "";
for(var iChar = 0; iChar < line.length; iChar++){
var char = fixedCharAt(line,iChar);
if(iChar != line.length - 1 && char != " "){
if(char == "S" && fixedCharAt(line,iChar + 1) == "'"){
formatedLine += char;
}else{
formatedLine += char+" ";
}
}else if(char != " "){
formatedLine += char;
}
}
formatedContent += formatedLine.trim();
if(iLine != lines.length - 1){
formatedContent += "\n";
}
}
info.content = formatedContent;
var raphObj = this.visualGraph.getRaphaelsFromID(vertex);
raphObj[3].attr({"text":info.content});
}
};
this.contentValidation = function(content,id) {
if(content){
var lines = content.split('\n');
for(var line of lines){
var nDots = 0;
for(var iChar = 0; iChar < line.length; iChar++){
var char = fixedCharAt(line,iChar);
if(!self.isCharValid(char)){
if(!((iChar > 0 && self.isPairValid(line.charAt(iChar - 1)+char)) || (iChar < line.length - 1 && self.isPairValid(char+line.charAt(iChar + 1))))){
self.displayError(char+" is not a valid symbol for this grammar");
return false;
}
}
if(char == "." || char == dot){
nDots++;
}
if(nDots > 1){
self.displayError("There shouldn't be more than one dot in a single line");
return false;
}
}
}
}
return true
};
this.vertexLabelValidation = function(id,label) {
if(label.length == 0 || label.length > 2) {
return false
}
var vertices = self.graph.getAllVertices();
for(var vertex of vertices){
var info = self.graph.getVertexInfo(vertex);
if(vertex != id && info.label == label){
return false
}
}
return true
};
this.writeContentCallback = function(id,edited) {
self.formatVertexContent(id);
if(edited){
self.visualGraph.redraw();
self.graphEditor.updateHandlers();
}
};
this.resizeTableVertexCallback = function(id,vertexPos,newBoxSize) {
var info = self.graph.getVertexInfo(id);
var raphObj = self.visualGraph.getRaphaelsFromID(id);
if(info.terminal){
raphObj[5].transform("");
raphObj[5].attr({
x: vertexPos.x - newBoxSize.w/2 - 5,
y: vertexPos.y - newBoxSize.h/2 - 5,
height: newBoxSize.h + 10,
width: newBoxSize.w + 10
});
}
if(info.initial){
raphObj[5].hide();
}
raphObj[4].transform("");
raphObj[4].attr({
x: vertexPos.x - newBoxSize.w/2 ,
y: vertexPos.y - newBoxSize.h/2 ,
width: newBoxSize.w,
"clip-rect": (vertexPos.x - newBoxSize.w/2)+" "+(vertexPos.y - newBoxSize.h/2)+" "+newBoxSize.w+" "+2*self.vertexLabelAttr["font-size"]
});
};
this.styleVertex = function(id,styleType) {
// console.log(id+" "+styleType)
var vertex = this.visualGraph.getRaphaelsFromID(id);
switch(styleType){
case "current":
vertex[0].attr(this.defaultCurrentStateAttr);
vertex[4].attr(this.currentHeaderAttr);
break;
case "selected":
vertex[0].attr(this.defaultSelectedVertexAttr);
vertex[4].attr(this.defaultSelectedVertexAttr);
break;
default:
vertex[0].attr(this.defaultVertexAttr);
vertex[4].attr(this.headerAttr);
}
vertex[1].toFront();
};
this.startDragCallback = function(id) {
var vertex = self.visualGraph.getRaphaelsFromID(id);
vertex[1].toFront();
vertex[2].toFront();
};
this.moveDragCallback = function(id) {
var vertex = self.visualGraph.getRaphaelsFromID(id);
var labelHeight = 2*self.vertexLabelAttr["font-size"];
var w = vertex[0].attr("width");
var h = vertex[0].attr("height");
var xRect = vertex[0].attr("x");
var yRect = vertex[0].attr("y");
vertex[4].attr("clip-rect",xRect+" "+yRect+" "+w+" "+labelHeight);
};
this.graphDraggerMoveDragCallback = function() {
var vertices = self.graph.getAllVertices();
for(var vertex of vertices){
self.moveDragCallback(vertex);
}
};
function fixedCharAt(str, idx) {
var ret = '';
str += '';
var end = str.length;
var surrogatePairs = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
while ((surrogatePairs.exec(str)) != null) {
var li = surrogatePairs.lastIndex;
if (li - 2 < idx) {
idx++;
} else {
break;
}
}
if (idx >= end || idx < 0) {
return '';
}
ret += str.charAt(idx);
if (/[\uD800-\uDBFF]/.test(ret) && /[\uDC00-\uDFFF]/.test(str.charAt(idx+1))) {
// On avance d'un puisque l'un des caractères fait partie de la paire
ret += str.charAt(idx+1);
}
return ret;
}
this.isCharValid = function(char) {
if(this.grammar.alphabet.includes(char) || char == "." || char == dot || char == arrow || char == " " || char == "$" || char == ""){
return true
}else{
return false
}
};
this.isPairValid = function(pair) {
if(pair == "S'" || pair == "->"){
return true
}
return false
};
/* parse table events */
this.clickCell = function(ev) {
self.resetFeedback();
var cell = $(this);
var state = $(this).attr("data_state");
var symbol = $(this).attr("data_symbol");
var cellContent = $(this).text();
if(self.mode == 4){
var maxlength = (self.lrTable.states.length >= 10) ? 3 : 2;
$(this).off("click");
if(self.cellEditor){
self.cellEditor.remove();
}
self.cellEditor = $("<input id=\"cellEditor\" value=\""+cellContent+"\" maxlength=\""+maxlength+"\">");
$(this).html(self.cellEditor);
self.styleCellEditor(state,symbol);
self.cellEditor.focus();
self.cellEditor.focusout(function(ev){
var text = $(this).val();
self.writeCell(text,cell);
});
self.cellEditor.keyup(function(ev){
var text = $(this).val();
if(ev.which == 13){
self.writeCell(text,cell);
}
});
}else{
ev.stopPropagation();
if(self.waitingForGoto){
var expectedSymbol = self.stack[self.stack.length - 1][1];
}else{
var expectedSymbol = self.input[self.inputIndex];
}
if(state != self.currentState){
self.displayError("Wrong state");
}else if(symbol != expectedSymbol){
self.displayError("Wrong symbol");
}else{
if(cellContent.length == 0){
}else if(cellContent[0] == "s"){
var nextState = cellContent[1];
self.actionSequence.push({
actionType: "s",
state: nextState
});
self.simulationStep++;
self.applyShift(nextState,false,true);
self.saveAnswer();
}else if(cellContent[0] == "r"){
var displayedRule = cellContent[1];
var rule = displayedRule - 1;
var nonTerminal = self.grammar.rules[rule].nonterminal;
self.treeAnim(self.simulationStep,false,true);
var nbRedChar = (self.grammar.rules[rule].development[0] == "''") ? 0 : self.grammar.rules[rule].development.length;
var startIndex = self.stack.length - nbRedChar;
for(var iSelCol = 0; iSelCol < nbRedChar; iSelCol++){
var index = startIndex + iSelCol;
$(".stackElement[data_col="+index+"]").addClass("selected");
self.selectedStackElements.push(index);
}
self.styleStackTable();
$(".rule").removeClass("selected");
$(".rule[data_rule="+rule+"]").addClass("selected");
self.selectedRule = rule;
self.styleRules();
var previousState = self.getPreviousState();
var goto = (nonTerminal != "S") ? self.lrTable.states[previousState][nonTerminal][0].actionValue : self.getTerminalState();
self.updateParseTableHL({anim:true,action:"startReduction",nonTerminal:nonTerminal});
self.simulationStep++;
self.applyReduction(nonTerminal,goto,true,true);
}else{ // goto
self.actionSequence.push({
actionType: "r",
rule: self.selectedRule,
goto: cellContent
});
self.currentState = cellContent;
self.goto(cellContent);
self.waitingForGoto = false;
self.saveAnswer();
}
}
}
};
this.writeCell = function(text,cell) {
self.cellEditor.remove();
cell.text(text);
cell.click(self.clickCell);
var state = cell.attr("data_state");
var symbol = cell.attr("data_symbol");
if(!answer[state])
answer[state] = {};
answer[state][symbol] = text;
// console.log(answer);
};
this.checkParseTable = function() {
// console.log(self.lrTable.states)
for(var state of self.lrTable.states){
if(!answer[state.index]){
self.displayError("Line "+state.index+" is empty");
return false;
}
for(var symbol in answer[state.index]){
if(answer[state.index][symbol] != ""){
if(symbol != "S" && !state[symbol]){
self.displayError("Error in line "+state.index+" at column "+symbol);
return false;
}
if(symbol == "S" && (state.index != 0 || answer[state.index][symbol] != self.lrTable.states.length)){
self.displayError("Error in line "+state.index+" at column "+symbol);
return false;
}
}
}
for(var symbol in state){
if(symbol != "index"){
if(!answer[state.index][symbol]){
self.displayError("An entry is missing in line "+state.index);
return false;
}
if(state[symbol][0].actionType == "r"){
var expected = "r"+(state[symbol][0].actionValue + 1);
}else{
var expected = state[symbol][0].actionType+state[symbol][0].actionValue;
}
if(answer[state.index][symbol] != expected){
console.log(expected)
self.displayError("Error in line "+state.index+" at column "+symbol);
return false;
}
}
}
if(!answer[0]["S"]){
self.displayError("Error in line 0 at column S");
return false;
}
}
return true;
};
/** derivation tree **/
this.updateTree = function() {
// console.log("updateTree");
this.updateTreePaper();
this.fixConflict();
this.treeClickableElements = {};
this.displayTree(this.derivationTree,0,null);
// console.log(this.derivationTree)
};
this.updateTreePaper = function() {
var treeHeight = this.getObjectDepth(this.derivationTree);
var treeWidth = this.getTreeWidth(this.derivationTree,1,2 * this.input.length - 1);
var w = treeWidth * this.treeCharSize;
var h = (2 * treeHeight + 1)*this.treeCharAttr["font-size"];
if(!this.treePaper){
this.treePaper = subTask.raphaelFactory.create("treePaper","treePaper",w,h);
}else{
this.treePaper.clear();
this.treePaper.setSize(w,h);
}
};
this.displayTree = function(tree,level,parentCol) {
// console.log("displayTree");
var children = [];
for(var col in tree){
if(col == "nonTerminal" || col == "path"){
continue;
}
var symbol = tree[col].nonTerminal || tree[col];
var x = col * this.treeCharSize;
var y = this.treeCharAttr["font-size"] * (2 * level + 1);
if((this.mode == 6 && level == 0) || (this.mode == 7 && this.grammar.nonterminals.includes(symbol) && typeof tree[col] != 'object')){
var clickArea = this.treePaper.circle(x,y,this.treeCharSize).attr({
fill: "white",
stroke: "none",
opacity: 0
});
var text = this.treePaper.text(x,y,symbol).attr(this.treeCharAttr);
var raphObj = this.treePaper.set(clickArea,text);
if(this.mode == 6){
this.treeClickableElements[col] = {raphObj:raphObj,symbol:symbol,selected:false};
}else{
this.treeClickableElements[col] = {raphObj:raphObj,symbol:symbol,selected:false,path:tree["path"]};
if(symbol != this.grammar.axiom)
children.push(col);
}
}else{
var text = this.treePaper.text(x,y,symbol).attr(this.treeCharAttr);
if(symbol != this.grammar.axiom)
children.push(col);
}
if(typeof tree[col] == 'object'){
this.displayTree(tree[col],level + 1,col);
}
}
if(children.length == 1){
var col = children[0];
var xi = col * this.treeCharSize;
var yi = this.treeCharAttr["font-size"] * (2 * level + 1/2);
var yf = this.treeCharAttr["font-size"] * (2 * level - 1/2);
this.treePaper.path("M"+xi+" "+yi+"V"+yf).attr(this.treeLineAttr);
}else{
for(var child of children){
var col = child;
var xi = col * this.treeCharSize;
var yi = this.treeCharAttr["font-size"] * (2 * level + 1/2);
var xf = parentCol * this.treeCharSize;
var yf = this.treeCharAttr["font-size"] * (2 * level - 1/2);
if(xi == xf){
this.treePaper.path("M"+xi+" "+yi+"V"+yf).attr(this.treeLineAttr);
}else{
this.treePaper.path("M"+xi+" "+yi+" C "+xi+","+yf+" "+xf+","+yi+" "+xf+","+yf).attr(this.treeLineAttr);
}
}
}
};
this.selectSymbol = function(index) {
return function() {
// console.log("click");
// console.log(self.selectedSymbolIndices);
index = parseFloat(index);
self.resetFeedback();
var selectedElement = self.treeClickableElements[index];
var selected = selectedElement.selected;
var x = selectedElement.raphObj[0].attr("cx");
var y = selectedElement.raphObj[0].attr("cy");
var r = self.treeCharSize * 0.7;
// if(self.mode == 6){
// // var selectedIndices = self.getSelectedIndices();
// self.selectedSymbolIndices.push(index);
// }
if(self.treeSelectionMarker){
self.treeSelectionMarker.remove();
}
if(selected){
if(self.mode == 6){
for(var i of self.selectedSymbolIndices){
// console.log("deselect "+i)
self.selectElement(self.treeClickableElements[i],false);
self.treeClickableElements[i].raphObj.unhover();
}
self.selectedSymbolIndices = [];
// for(var iEl in self.treeClickableElements){
// self.selectElement(self.treeClickableElements[iEl],false);
// self.treeClickableElements[iEl].raphObj.unhover();
// }
}else{
self.selectElement(selectedElement,false);
}
}else{
self.selectedSymbolIndices = [];
for(var iEl in self.treeClickableElements){
// console.log("deselect "+iEl)
self.selectElement(self.treeClickableElements[iEl],false);
if(iEl != index && self.mode == 6){
self.treeClickableElements[iEl].raphObj.hover(self.hoverIn(iEl),self.hoverOut(iEl));
}
$(window).mouseup(self.dragEnd);
}
self.selectedSymbolIndices.push(index);
self.selectElement(selectedElement,true);
self.treeSelectionMarker = self.treePaper.circle(x,y,r).attr(self.treeSelectionMarkerAttr).toBack();
}
}
};
this.hoverIn = function(id) {
id = parseInt(id);
return function() {
if(Beav.Array.has(self.selectedSymbolIndices,id)){
return
}else{
self.selectedSymbolIndices.push(id);
self.selectElement(self.treeClickableElements[id],true);
self.updateTreeSelection();
}
}
};
this.hoverOut = function(id) {
return function() {
}
};
this.updateTreeSelection = function() {
var orderedSelectedIndices = self.selectedSymbolIndices.sort(function(a,b){return (a - b)});
// console.log(orderedSelectedIndices)
var smallerIndex = orderedSelectedIndices[0];
var furthestIndex = orderedSelectedIndices[orderedSelectedIndices.length - 1];
// var furthestIndex = self.getFurthestIndex(smallerIndex,self.selectedSymbolIndices);
var x = self.treeClickableElements[smallerIndex].raphObj[0].attr("cx");
var y = self.treeClickableElements[smallerIndex].raphObj[0].attr("cy");
var fx = self.treeClickableElements[furthestIndex].raphObj[0].attr("cx");
var fy = self.treeClickableElements[furthestIndex].raphObj[0].attr("cy");
var r = self.treeCharSize * 0.7;
var w = Math.abs(x - fx) + 2 * r;
var h = 2 * r;
// if(index < furthestIndex){
// for(var i in self.treeClickableElements){
// if(parseFloat(i) >= index && parseFloat(i) <= furthestIndex)
// self.selectElement(self.treeClickableElements[i],true);
// }
self.treeSelectionMarker.remove();
self.treeSelectionMarker = self.treePaper.rect(x - r,y - r,w,h,r).attr(self.treeSelectionMarkerAttr);
self.treeSelectionMarker.toBack();
// }else{
// for(var i in self.treeClickableElements){
// if(parseFloat(i) <= smallerIndex && parseFloat(i) >= furthestIndex)
// self.selectElement(self.treeClickableElements[i],true);
// }
// self.treeSelectionMarker.remove();
// self.treeSelectionMarker = self.treePaper.rect(fx - r,fy - r,w,h,r).attr(self.treeSelectionMarkerAttr).toBack();
// }
};
this.dragEnd = function() {
for(var iEl in self.treeClickableElements){
self.treeClickableElements[iEl].raphObj.unhover();
}
$(window).off("mouseup");
};
this.getSelectedIndices = function() {
var selectedEl = [];
for(var col in this.treeClickableElements){
var el = this.treeClickableElements[col];
if(el.selected){
selectedEl.push(col);
}
}
return selectedEl;
};
// this.getFurthestIndex = function(index,selectedIndices) {
// var diff = 0;
// var furthestIndex;
// for(var i of selectedIndices){
// var localDiff = Math.abs(i - index);
// if(localDiff >= diff){
// diff = localDiff;
// furthestIndex = i;
// }
// }
// // console.log(furthestIndex);
// return furthestIndex;
// };
this.selectElement = function(el,selected){
// console.log(el.symbol+" "+selected);
if(selected){
el.selected = true;
el.raphObj[1].attr({fill:"white"});
}else{
el.selected = false;
el.raphObj[1].attr({fill:self.colors.blue});
}
};
this.getObjectDepth = function(object) {
var level = 1;
var key;
for(key in object) {
if (!object.hasOwnProperty(key)) continue;
if(typeof object[key] == 'object'){
var depth = this.getObjectDepth(object[key]) + 1;
level = Math.max(depth, level);
}
}
return level;
};
this.getTreeWidth = function(tree,min,max) {
var min = this.getMinKey(tree,min);
var max = this.getMaxKey(tree,max);
var width = max - min + 3;
return width;
};
this.getMinKey = function(tree,min) {
for(var key in tree){
if(!isNaN(key)){
key = parseFloat(key);
min = Math.min(key,min);
if(typeof tree[key] == 'object'){
var localMin = this.getMinKey(tree[key],min);
min = Math.min(localMin,min);
}
}
}
return min;
};
this.getMaxKey = function(tree,max) {
for(var key in tree){
if(!isNaN(key)){
key = parseFloat(key);
max = Math.max(key,max);
if(typeof tree[key] == 'object'){
var localMax = this.getMaxKey(tree[key],max);
max = Math.max(localMax,max);
}
}
}
return max;
};
this.produce = function() {
var selectedIndices = self.getSelectedIndices();
if(self.selectedRule == null){
self.displayError("You must select a rule");
}else if(selectedIndices.length == 0){
self.displayError("You must select at least one symbol");
}else if(!self.compareSelectedRuleAndStack()){
self.displayError("You cannot develop the selected symbol with the selected rule");
}else{
var rule = self.grammar.rules[self.selectedRule];
var nonTerminal = rule.nonterminal;
var newEntry = {};
var parentIndex = selectedIndices[0];
// var level = self.treeClickableElements[parentIndex].level;
var path = self.treeClickableElements[parentIndex].path;
var startIndex = parentIndex - rule.development.length + 1;
for(var iSymbol = 0; iSymbol < rule.development.length; iSymbol++){
var index = startIndex + iSymbol * 2;
newEntry[index] = rule.development[iSymbol];
}
newEntry.nonTerminal = nonTerminal;
// console.log(self.treeClickableElements[parentIndex]);
if(!path){
newEntry.path = [parentIndex];
self.derivationTree[parentIndex] = newEntry;
}else{
newEntry.path = JSON.parse(JSON.stringify(self.treeClickableElements[parentIndex].path));
newEntry.path.push(parentIndex);
self.addNewEntry(newEntry);
}
self.selectedSymbolIndices = [];
self.unselectRules();
self.updateTree();
self.initHandlers();
self.saveAnswer();
// console.log(self.derivationTree);
}
};
this.addNewEntry = function(newEntry) {
var path = JSON.parse(JSON.stringify(newEntry.path));
var branch = this.goToBranch(this.derivationTree,path);
branch[path[path.length - 1]] = newEntry;
// branch = newEntry;
};
this.goToBranch = function(tree,path) {
var step = path.shift();
var branch = tree[step];
if(path.length > 1){
branch = this.goToBranch(branch,path);
}
return branch;
};
this.fixConflict = function() {
var negativeKey = this.findNegativeKey(this.derivationTree);
if(negativeKey){
var newTree = {};
var correction = 1 - negativeKey;
this.shiftTree(this.derivationTree,correction,newTree);
this.derivationTree = JSON.parse(JSON.stringify(newTree));
this.updateTreePaper();
}
this.fixOverlap(this.derivationTree);
};
this.findNegativeKey = function(object) {
var negativeKey = 0;
for(var key in object) {
if(key < negativeKey){
negativeKey = key;
}
if (!object.hasOwnProperty(key)) continue;
if(typeof object[key] == 'object'){
var otherKey = this.findNegativeKey(object[key]);
negativeKey = Math.min(negativeKey, otherKey);
}
}
return negativeKey;
};
this.shiftTree = function(tree,correction,nTree) {
for(var key in tree) {
if(key != "nonTerminal" && key != "path"){
key = parseFloat(key);
var newKey = key + correction;
// console.log(key+" "+correction+" "+newKey);
if(typeof tree[key] == 'object'){
nTree[newKey] = {};
this.shiftTree(tree[key],correction,nTree[newKey]);
}else{
nTree[newKey] = JSON.parse(JSON.stringify(tree[key]));
}
}else if(key == "nonTerminal"){
nTree[key] = JSON.parse(JSON.stringify(tree[key]));
}else if(key == "path"){
nTree[key] = tree[key].map(x => parseFloat(x) + correction);
}
}
};
this.fixOverlap = function(tree) {
this.inputBaseline = [];
this.getInputBaseLine(this.derivationTree);
var baseLength = this.inputBaseline.length;
if(baseLength >= this.input.length){
var minIndex = 1;
}else{
var minIndex = this.input.length - baseLength + 1;
}
if(!this.isOverlapping(minIndex)){
return;
}
var maxIndex = minIndex + (baseLength - 1) * 2;
var index = maxIndex;
do{
var entry = this.inputBaseline.pop();
if(entry.key != index){
if(entry.path){
var path = JSON.parse(JSON.stringify(entry.path));
var branch = this.goToBranch(this.derivationTree,path);
if(path.length > 0){
branch = branch[path[0]];
}
}else{
var branch = this.derivationTree;
}
this.replaceLeafIndex(branch,entry.key,index);
}
index = index - 2;
}while(this.inputBaseline.length > 0);
this.fixParentsPositions(this.derivationTree);
this.updateTreePaper();
};
this.getInputBaseLine = function(tree) {
for(var key in tree){
if(typeof tree[key] != 'object'){
if(key != "nonTerminal"){
this.inputBaseline.push({symbol:tree[key],key:key,path:tree.path});
}
}else if(key != "path"){
this.getInputBaseLine(tree[key]);
}
}
};
this.replaceLeafIndex = function(branch,oldIndex,newIndex) {
if(branch[newIndex]){
this.replaceLeafIndex(branch,newIndex,newIndex + 2);
}
if(branch[oldIndex]){
branch[newIndex] = JSON.parse(JSON.stringify(branch[oldIndex]));
delete branch[oldIndex];
}
};
this.isOverlapping = function(minIndex) {
var index = minIndex;
for(var el of this.inputBaseline){
if(el.key != index){
return true;
}else{
index = index + 2;
}
}
return false;
};
this.fixParentsPositions = function(tree) {
for(var pos in tree){
if(typeof tree[pos] == "object" && pos != "path"){
this.fixParentsPositions(tree[pos]);
var sum = 0;
var cpt = 0;
for(var childPos in tree[pos]){
if(!isNaN(childPos)){
sum += parseInt(childPos);
cpt++;
}
}
var newPos = Math.round(sum/cpt);
if(pos != newPos){
this.replaceBranchIndex(tree,pos,newPos);
}
}
}
};
this.replaceBranchIndex = function(parentBranch,oldIndex,newIndex) {
parentBranch[newIndex] = JSON.parse(JSON.stringify(parentBranch[oldIndex]));
this.replaceBranchPath(parentBranch[newIndex],newIndex,parentBranch[newIndex].path.length - 1);
delete parentBranch[oldIndex];
};
this.replaceBranchPath = function(branch,newIndex,level) {
branch.path[level] = newIndex;
for(var index in branch){
if(typeof branch[index] == "object" && !isNaN(index)){
this.replaceBranchPath(branch[index],newIndex,level);
}
}
};
/* answer */
this.saveAnswer = function() {
switch(self.mode){
case 2:
case 5:
answer.actionSequence = JSON.parse(JSON.stringify(self.actionSequence));
answer.accept = self.accept;
answer.error = self.error;
break;
case 3:
answer.visualGraphJSON.push(self.visualGraph.toJSON());
break;
case 6:
case 7:
answer.push(JSON.parse(JSON.stringify(self.derivationTree)));
if(answer.length > 1){
self.enableUndoButton();
}
break;
}
};
this.reloadAnswer = function() {
this.resetFeedback();
switch(this.mode){
case 2:
case 5:
this.actionSequence = JSON.parse(JSON.stringify(answer.actionSequence));
this.replayUpTo(this.actionSequence.length,false,true);
if(answer.accept){
this.acceptInput();
}else if(answer.error){
this.refuseInput();
}
break;
case 3:
this.visualGraphJSON = answer.visualGraphJSON[answer.visualGraphJSON.length - 1];
this.initAutomata();
this.updateState(false,"reloadAnswer");
// this.graphEditorCallback();
this.updateParseTable();
break;
case 4:
for(var state in answer){
if(answer[state]){
for(var symbol in answer[state]){
$("#"+this.parseTableID+" table td[data_symbol=\""+symbol+"\"][data_state=\""+state+"\"]").text(answer[state][symbol])
}
}
}
break;
case 6:
case 7:
this.derivationTree = JSON.parse(JSON.stringify(answer[answer.length - 1]));
this.updateTree();
this.initHandlers();
if(answer.length > 1){
self.enableUndoButton();
}else{
self.disableUndoButton();
}
}
};
this.undo = function() {
self.resetFeedback();
if(self.mode < 6){
answer.visualGraphJSON.pop();
self.reloadAnswer();
if(answer.visualGraphJSON.length <= 1){
self.disableUndoButton()
}
}else{
answer.pop();
self.reloadAnswer();
// if(answer.length <= 1){
// self.disableUndoButton();
// }
}
};
this.validation = function() {
switch(this.mode){
case 2:
case 5:
this.pauseSimulation();
if(!answer.accept && !answer.error){
this.displayError("You must click on either the accept or the error button");
}else{
this.reset();
this.actionSequence = [];
this.initActionSequence(true);
var lastAction = this.actionSequence[this.actionSequence.length - 1];
var accept = false;
if(lastAction.actionType == "r" && lastAction.goto == this.getTerminalState()){
accept = true;
}
if(answer.actionSequence.length != this.actionSequence.length){
this.displayError("Your parsing is incomplete");
}else if(answer.accept && !accept){
this.displayError("Wrong answer");
}else if(answer.error && accept){
this.displayError("Wrong answer");
}else{
return true;
}
}
this.reset();
this.actionSequence = JSON.parse(JSON.stringify(answer.actionSequence));
this.replayUpTo(this.actionSequence.length,false,true);
break;
case 3:
this.pauseSimulation();
var lrClosureTable = this.lrClosureTable.kernels;
var vertices = this.graph.getAllVertices();
var terminalState = this.getTerminalState();
if(vertices.length != lrClosureTable.length + 1){
this.displayError("The number of states in your automaton is incorrect")
}else if(!terminalState){
this.displayError("Wrong terminal state")
}else{
var success = true;
for(var vertex of vertices){
var info = this.graph.getVertexInfo(vertex);
if(info.terminal){
continue
}
var stateIndex = this.getStateIndex(vertex);
if(stateIndex == null){
this.displayError("Error in state "+info.label);
return
}
if(stateIndex == 0 && !info.initial){
this.displayError("State "+info.label+" is not set as initial");
return
}
var state = lrClosureTable[stateIndex];
var nGoto = state.keys.length;
var children = this.graph.getChildren(vertex);
if((state.index == 0 && children.length != nGoto + 1) || (state.index != 0 && children.length != nGoto)){
this.displayError("The number of transitions from state "+info.label+" is incorrect");
return
}
for(var child of children){
var edges = this.graph.getEdgesFrom(vertex,child);
var edgeInfo = this.graph.getEdgeInfo(edges[0]);
var childInfo = this.graph.getVertexInfo(child);
var childState = this.getStateIndex(child);
if(childState == null && !childInfo.terminal){
this.displayError("Error in state "+childInfo.label);
return
}
if(state.gotos[edgeInfo.label] != childState && !(state.index == 0 && terminalState == childInfo.label)){
this.displayError("Wrong transition from state "+info.label+" to state "+childInfo.label);
return
}
}
}
return success;
}
break;
case 4:
this.pauseSimulation();
var success = this.checkParseTable();
return success;
case 6:
var lastStep = answer[answer.length - 1];
var keys = Object.keys(lastStep);
if(keys.length == 1 && lastStep[keys[0]].nonTerminal == "S"){
return true;
}else{
this.displayError("You didn't reduce the entire input to the axiom");
}
break;
case 7:
var lastStep = answer[answer.length - 1];
this.inputBaseline = [];
this.getInputBaseLine(lastStep);
var index = 0;
for(var el of this.inputBaseline){
if(el.symbol != this.input.charAt(index)){
this.displayError("You didn't reach the objective");
return false;
}
index++;
}
return true;
}
};
this.getStateIndex = function(vID) {
/* validation in mode 3 */
var content = this.readContent(vID);
if(!content){
return null
}
var stateIndex = null;
for(var state of this.lrClosureTable.kernels){
if((state.index != 0 && content.length != state.closure.length) ||
(state.index == 0 && (content.length > state.closure.length + 1 ||
content.length < state.closure.length))){
continue
}
var match = true;
for(var item of state.closure){
var rule = item.rule.index;
var dotIndex = item.dotIndex;
var itemFound = false;
for(var line of content){
var lineInfo = this.readLine(line);
if(lineInfo.ruleIndex == rule && lineInfo.dotIndex == dotIndex){
itemFound = true;
break;
}
}
if(!itemFound){
match = false;
break;
}
}
if(match){
stateIndex = state.index;
}
}
return stateIndex;
};
this.displayMessage = function(type,message) {
if(type == "reset"){
$(".actionMessage").text("");
}else{
$("#"+type+"Message").text(message);
}
};
this.displayError = function(msg) {
$("#error").text(msg);
// console.log(msg)
};
this.resetFeedback = function() {
// console.log("reset")
self.displayMessage("reset");
self.displayError("");
self.displayExplanation(false);
};
this.style = function() {
// TODO :: put all that into the CSS file...
// console.log("style")
$("#"+this.divID).css({
"font-size": "80%"
})
if(this.mode < 6){
/* tab switch */
this.styleTabSwitch();
/* paper, parse table */
this.styleTabs();
}
/* parse info */
$("#parseInfo").css({
display: "flex",
"justify-content": "center",
"align-items": "flex-start"
});
$("#parseInfo > *").css({
"box-sizing": "border-box"
});
/* rules */
this.styleRules();
/* action */
$("#action").css({
"margin-left": "15px",
"flex-grow": "20"
});
$("#action h4").css({
color: "grey",
"font-weight": "normal",
"margin-bottom": "0.5em",
});
if(this.mode < 6){
/* player */
$("#player").css({
display: "flex",
"align-items": "center",
"justify-content": "space-between",
padding: "10px",
"border-radius": "25px",
"background-color": this.colors.lightgrey
});
$("#player > *").css({
color: "white",
"font-size": "1em",
"border-radius": "2em",
"text-align": "center",
"box-sizing": "border-box"
});
$("#play").css({
"background-color": this.colors.blue,
padding: "10px 12px",
"margin-right": "10px"
});
$("#stepBackward, #stepForward, #undo").css({
"background-color": this.colors.black,
padding: "10px 20px",
"margin-left": "10px"
});
$("#undo").css({
"font-weight": "bold"
})
if(this.mode == 3){
this.showUndo();
}
this.styleProgressBar();
/* stack */
this.styleStackTable();
/* action button */
$("#acceptButton, #errorButton").css({
"border-radius": "1em",
float: "right",
"margin-top": "1em",
clear: "right"
});
$(".actionButton").css({
"background-color": this.colors.blue,
color: "white",
width: "110px",
padding: "0.5em 0",
"text-align": "center",
"font-weight": "bold",
"font-size": "0.9em"
});
var buttonHeight = $(".actionButton").innerHeight();
$("#reduceBar, #shiftBar").css({
display: "flex",
"justify-content": "flex-end",
});
$(".actionMessage").css({
padding: "0.5em 1em",
color: "grey",
"background-color": "white",
"border-radius": "0 5px 0 0",
height: buttonHeight+"px",
"box-sizing": "border-box"
});
$(".messageBackground").css({
"background-color": this.colors.blue,
height: buttonHeight+"px"
})
$("#reduceButton, #shiftButton").css({
"border-radius": "0 0 5px 5px"
});
// $("#acceptButton, #errorButton").css({
// "margin-left": "1em"
// });
$(".buttonIcon").css({
"font-size": "0.9em",
"margin-right": "0.2em"
});
$("#shiftButton .buttonIcon").css({
"margin-right": "0.5em"
});
/* input */
this.styleInput();
/* cursor */
$("#cursor").css({
position: "absolute",
top: 0,
left: 0,
height: "2em"
});
$("#cursorBar").css({
height: "2em",
width: 0,
border: "1px solid "+this.colors.blue,
position: "absolute",
top: 0,
left: 0
});
$("#topCircle, #bottomCircle").css({
width: "6px",
height: "6px",
"background-color": "white",
border: "1px solid "+this.colors.blue,
"border-radius": "5px",
position: "absolute",
left: "-3px",
"z-index": 2
});
$("#topCircle").css({
top: "-4px"
});
$("#bottomCircle").css({
bottom: "-6px"
});
this.styleDerivationTree();
}else{
$("#tree").css({
display: "flex",
"justify-content": "flex-start",
"align-items": "flex-start"
});
$("#reduceButton, #produceButton, #undo").css({
"border-radius": "1em",
"background-color": this.colors.blue,
color: "white",
width: "110px",
padding: "0.5em 0",
"text-align": "center",
"font-weight": "bold",
"font-size": "0.9em",
});
$(".buttonIcon").css({
"font-size": "0.9em",
"margin-right": "0.2em"
});
$("#reduceButton, #produceButton").css({
"margin-bottom": "1em"
});
$(".inputChar").css({
display: "inline-block",
width: this.treeCharSize,
"text-align": "center",
color: this.colors.black,
"font-size": this.treeCharSize,
padding: "0.5em "+0.5*this.treeCharSize+"px"
});
}
};
this.styleTabSwitch = function() {
if(this.sideTable){
$("#"+this.tabsID).hide();
return
}else{
$("#"+this.tabsID).show();
}
$("#"+this.tabsID).css({
"text-align": "right",
margin: "1em 0"
});
$("#"+this.tabsID+" > *").css({
display: "inline-block",
"vertical-align": "middle",
"font-weight": "bold",
color: this.colors.black,
margin: "0 0.2em"
});
$("#"+this.tabsID+" span").css(this.unselectedTabAttr);
$("#"+this.tabsID+" span.selectedTab").css(this.selectedTabAttr);
$("#switchContainer").css({
width: "50px",
height: "15px",
"background-color": this.colors.lightgrey,
"border-radius": "10px",
"box-shadow": "inset 0 1px 0 0 rgba(0,0,0,0.2)",
cursor: "pointer",
position: "relative"
});
$("#switch").css({
width: "25px",
height: "15px",
"background-color": this.colors.blue,
"border-radius": "10px",
position: "absolute",
top: 0,
left: this.selectedTab*25+"px"
});
};
this.styleTabs = function() {
if(!this.sideTable){
$("#"+this.graphPaperID).css({
width: this.paperWidth
});
$("#"+this.graphPaperID+", #"+this.parseTableID).css({
margin: "1em auto",
height: this.paperHeight
});
}else{
$("#"+this.tabsContainerID).css({
display: "flex",
"flex-direction": "row",
"justify-content": "space-around",
margin: "1em 0",
});
}
this.styleParseTable();
};
this.styleParseTable = function() {
// console.log("styleParseTable")
if(!this.sideTable){
$("#"+this.parseTableID+" table").css({
"font-size": "1.2em"
});
$("#"+this.parseTableID+" table").css({
margin: "auto"
});
$("#"+this.parseTableID+" table th, #"+this.parseTableID+" table td").css({
padding: "0.4em 0.8em"
});
}else{
$("#"+this.parseTableID+" table th, #"+this.parseTableID+" table td").css({
padding: "0.2em 0.4em"
});
if(this.mode == 4){
$("#"+this.parseTableID+" table td[data_symbol]").css({
width: "1.5em"
});
}
}
$("#"+this.parseTableID).css({
position: "relative"
})
$("#"+this.parseTableID+" table").css({
"border-collapse": "collapse",
border: "2px solid "+this.colors.black,
"text-align": "center"
});
$("#"+this.parseTableID+" table th").css({
"background-color": this.colors.black,
color: "white",
border: "1px solid white"
});
$("#"+this.parseTableID+" td").css(this.cellAttr);
if(this.mode >= 4){
$("#"+this.parseTableID+" td[data_symbol]").css({
cursor: "pointer"
})
}
$("#"+this.parseTableID+" td .ruleMarker").css({
"background-color": this.colors.black,
"border-radius": "1em",
color: "white",
padding: "0.2em 0.5em"
});
$("#"+this.parseTableID+" td .ruleMarkerIndex").css({
color: this.colors.yellow,
// "font-weight": "bold"
});
$("#rowHL, #colHL").css(this.cellHighlightAttr);
// $("#"+this.parseTableID+" td.selected").css(this.selectedCellAttr);
};
this.styleRules = function() {
$("#rules").css({
"flex-grow": "1",
"flex-shrink": "0",
"background-color": this.colors.lightgrey,
"border-radius": "0 5px 5px 0",
"overflow": "hidden",
"font-weight": "bold"
});
$("#rules h3").css({
"text-align": "center",
padding: "1em",
margin: 0,
"background-color": this.colors.black,
color: "white",
"font-size": "1em"
});
$("#rules ul").css({
"list-style": "none",
padding: 0
});
$(".rule").css({
padding: "0.2em 0.5em 0.2em 0",
margin: "0.5em 1em",
"background-color": "transparent",
color: this.colors.black,
"border-radius": "1em"
});
$(".ruleIndex").css({
"flex-grow": "0",
"background-color": this.colors.black,
"border-radius": "1em",
// color: "white",
color: this.colors.yellow,
padding: "0.2em 0.5em",
"margin-right": "0.5em"
});
$(".rule i").css({
color: "grey",
margin: "0 0.5em"
});
$(".rule.previousRule").css({
"background-color": "#d9e3ef"
});
$(".rule.selected").css({
"background-color": this.colors.blue,
color: "white"
});
$(".rule.selected .ruleIndex").css({
"border-radius": "1em 0 0 1em",
});
$(".rule.selected i").css({
color: this.colors.yellow
});
$(".rule .epsilon").css({
"font-style": "italic"
})
};
this.styleProgressBar = function() {
$("#progressBarClickArea").css({
"flex-grow": "1",
height: "20px",
margin: "0 10px",
"padding-top": "8px"
});
$("#progressBarContainer").css({
height: "4px",
"background-color": "grey",
});
$("#progressBar").css({
width: "0%",
height: "100%",
"background-color": this.colors.blue,
position: "relative"
});
$("#progressBarMarker").css({
width: "6px",
height: "6px",
"background-color": "white",
border: "3px solid "+this.colors.blue,
"border-radius": "15px",
position: "absolute",
right: "-6px",
top: "-4px",
"z-index": 2
});
};
this.styleStackTable = function() {
$("#stackTableContainer").css({
position: "relative"
});
$("#stackTable").css({
border: "2px solid "+this.colors.blue,
"border-right": "none",
"border-collapse": "collapse",
"width": "100%"
});
$("#stackTable td").css({
border: "1px solid grey",
"background-color": this.colors.lightgrey,
"text-align": "center",
width: "2em",
height: "2em"
});
$("#stackTable td:last-child").css({
"border-right": "none",
color: "grey",
"text-align": "right",
"font-style": "italic",
"background-color": "white",
width: "auto"
});
$("#stackTable .State").css({
color: "grey"
});
$("#stackTable .Symbol").css({
color: this.colors.blue
});
$("#stackTable .stackElement.selected").css({
"background-color": this.colors.blue,
color: "white"
});
};
this.styleInput = function() {
$("#inputBar").css({
position: "relative",
"background-color": this.colors.lightgrey,
"margin-top": "5px",
"border-top": "1px solid grey",
"border-bottom": "2px solid "+this.colors.blue
});
$("#inputBar h4").css({
position: "absolute",
top: "-3em"
});
$(".inputChar").css({
display: "inline-block",
width: "1.5em",
"text-align": "center",
color: this.colors.black,
"font-size": "1.5em",
padding: "0.1em 0"
});
$(".inputChar.read").css({
color: this.colors.blue
});
$(".inputChar:first-of-type").css({
"margin-left": "0.75em"
});
};
this.styleDerivationTree = function() {
// if(this.mode < 6){
var charWidth = $("#inputBar .inputChar").width();
var charHeight = $("#inputBar .inputChar").height()*0.7;
var treeWidth = this.input.length*charWidth;
// }else{
// var charWidth = this.treeCharSize;
// var charHeight = this.treeCharSize*0.7;
// var treeWidth = (this.input.length + 1)*charWidth;
// }
// var treeWidth = this.input.length*charWidth;
var treeHeight = (2*this.treeHeight + 1)*charHeight;
$("#derivationTree").css({
position: "relative",
"padding-top": "10px",
width: this.input.length*charWidth
});
$("#derivationTree h4").css({
"margin": "0 0 1em 0"
});
$("#tree").css({
height: treeHeight,
width: treeWidth
});
var symbolAttr = {
"font-size": charHeight,
"font-weight": "bold",
"fill": this.colors.blue
};
if(!this.treePaper){
this.treePaper = subTask.raphaelFactory.create("tree","tree",treeWidth,treeHeight);
}else{
this.treePaper.clear();
this.treePaper.setSize(treeWidth,treeHeight);
}
for(var iLine = 0; iLine < 2*this.treeHeight + 1; iLine++){
// for(var iChar = 0; iChar < this.input.length - 1; iChar++){
for(var iCol = 0; iCol < this.derivationTree.length; iCol++){
if(iLine == 0){
// var x = charWidth * (iChar + 1/2);
var x = charWidth * (iCol + 1) / 2;
var y = treeHeight - charHeight/2;
// this.treePaper.text(x,y,this.input[iChar]).attr(symbolAttr);
if(iCol%2 == 1){
this.treePaper.text(x,y,this.input[(iCol - 1)/2]).attr(symbolAttr);
}
}else if(iLine % 2 != 0 && this.derivationTree[iCol]){
var rule = (this.derivationTree[iCol][(iLine - 1)/2]) ? this.derivationTree[iCol][(iLine - 1)/2][0] : "";
var actionIndex = (this.derivationTree[iCol][(iLine - 1)/2]) ? this.derivationTree[iCol][(iLine - 1)/2][1] : "";
if(this.grammar.rules[rule]){
var nbRed = this.grammar.rules[rule].development.length;
var x = (iCol%2 == 0 && nbRed > 1) ? (iCol + 1 - (nbRed - 2))*charWidth/2 : (iCol + 1 - (nbRed - 1))*charWidth/2;
var y = treeHeight - charHeight*(iLine + 1 + 1/2);
this.treeElements[actionIndex] = [this.treePaper.text(x,y,this.grammar.rules[rule].nonterminal).attr(symbolAttr)];
if(iLine == 1){
var x1 = x;
var x2 = x1;
var y1 = y + charHeight/2;
var y2 = y1 + charHeight;
this.treeElements[actionIndex].push(self.treePaper.path("M"+x1+" "+y1+" V"+y2).attr(self.treeLineAttr));
}else if(nbRed == 1){
var x1 = x;
var x2 = x1;
var y1 = y + charHeight/2;
var y2 = y1 + charHeight;
this.treeElements[actionIndex].push(self.treePaper.path("M"+x1+" "+y1+" V"+y2).attr(self.treeLineAttr));
}else{
var kLine = (iLine - 1)/2;
var children = [];
var childrenIndices = [iCol];
var jCol = iCol;
do{
jCol--;
if(this.derivationTree[jCol]){
childrenIndices.unshift(jCol);
}
}while(childrenIndices.length < nbRed);
// for(var j = iChar - (nbRed - 1); j <= iChar; j++){
for(var j of childrenIndices){
jLine = kLine - 1;
var foundChild = false;
do{
var rule = self.derivationTree[j][jLine][0];
if(rule != ""){
children.push({col:j,row:(2*jLine + 1),nbRed:self.grammar.rules[rule].development.length})
foundChild = true;
}else{
jLine--;
if(jLine < 0){
children.push({col:j,row:jLine,nbRed:1})
foundChild = true;
}
}
}while(!foundChild)
}
for(var child of children){
var childLeft = (child.col%2 == 0 && child.nbRed > 1) ? (child.col + 1 - (child.nbRed - 2))*charWidth/2 : (child.col + 1 - (child.nbRed - 1))*charWidth/2;
var x1 = x;
var x2 = childLeft;
var y1 = 2*self.treeHeight - iLine;
var y2 = y1 + 1;
if(x1 == x2){
this.treeElements[actionIndex].push(self.treePaper.path("M"+x1+" "+y1*charHeight+" V"+y2*charHeight).attr(self.treeLineAttr));
}else{
var x1c = x1;
var y1c = (y1 + 0.9);
var x2c = x2;
var y2c = (y2 - 0.9);
this.treeElements[actionIndex].push(self.treePaper.path("M"+x1+" "+y1*charHeight+" C "+x1c+","+y1c*charHeight+" "+x2c+","+y2c*charHeight+" "+x2+","+y2*charHeight).attr(self.treeLineAttr));
}
var x3 = x2;
var y3 = 2*self.treeHeight - child.row - 1;
this.treeElements[actionIndex].push(self.treePaper.path("M"+x2+" "+y2*charHeight+" V"+y3*charHeight).attr(self.treeLineAttr));
}
}
}
}
}
}
// if(this.mode < 6)
for(var actionIndex in this.treeElements){
var elements = this.treeElements[actionIndex];
for(var el of elements){
el.attr({opacity:0});
}
}
$("#treeCursor").css({
position: "absolute",
bottom: 0,
left: 0,
height: $("#derivationTree").outerHeight() + $("#shiftBar").height(),
width: 0,
"border-left": "1px dotted "+this.colors.blue
});
};
this.styleCellEditor = function(state,symbol) {
$("#"+this.parseTableID+" table td[data_symbol=\""+symbol+"\"][data_state=\""+state+"\"]").css({
padding: 0
})
$("#cellEditor").css({
width: "1.5em",
});
};
this.showUndo = function() {
if(this.isLastActionAGraphEdit){
$("#stepBackward, #stepForward").hide();
$("#undo").show();
}else{
$("#stepBackward, #stepForward").show();
$("#undo").hide();
}
}
this.init();
};