diff options
author | Patrick Simianer <p@simianer.de> | 2010-05-24 02:01:22 +0200 |
---|---|---|
committer | Patrick Simianer <p@simianer.de> | 2010-05-24 02:01:22 +0200 |
commit | f8f3a94e6d9572bef0849b15752b81d0402bfc90 (patch) | |
tree | 61864126a42b271bccf9f6289b0f1ea49115e57a | |
parent | 3d9e4d9737fdb534b07638ff5d47b0e74922fe7b (diff) |
finish!
-rw-r--r-- | javascripts/globals.js | 3 | ||||
-rw-r--r-- | javascripts/graph.js | 48 | ||||
-rw-r--r-- | javascripts/lib/raphael.js | 2 | ||||
-rw-r--r-- | javascripts/uifunc.js | 203 | ||||
-rw-r--r-- | regexvis.html | 43 | ||||
-rw-r--r-- | resources/urls.txt | 2 | ||||
-rw-r--r-- | stylesheets/styles.css | 40 |
7 files changed, 240 insertions, 101 deletions
diff --git a/javascripts/globals.js b/javascripts/globals.js index e8668b8..56b31d8 100644 --- a/javascripts/globals.js +++ b/javascripts/globals.js @@ -2,9 +2,10 @@ var EPSILON = '~'; var NEXTSTATE = 0; var EMPTYSYMBOL = '%'; -var ALPHABET = 'abc'+EMPTYSYMBOL; +var ALPHABET = 'abcd'+EMPTYSYMBOL; var ALPHABETS = ALPHABET+'()|*'; var REDELIMITER = '$'; var ttable = new Object(); var g; +var graphit = true; diff --git a/javascripts/graph.js b/javascripts/graph.js index 1d7d17d..32353e3 100644 --- a/javascripts/graph.js +++ b/javascripts/graph.js @@ -1,5 +1,5 @@ -// source: http://raphaeljs.com/graffle.html (extended with labels and arrow heads) -Raphael.fn.connection = function (obj1, obj2, line, bg, strokeColor, symbol, labelFontSize) { +// Source: http://raphaeljs.com/graffle.html (extended with labels and arrow heads) +Raphael.fn.connection = function (obj1, obj2, line, bg, strokeColor, symbol, labelFontSize, name1, name2) { if (obj1.line && obj1.from && obj1.to) { line = obj1; obj1 = line.from; @@ -72,18 +72,20 @@ Raphael.fn.connection = function (obj1, obj2, line, bg, strokeColor, symbol, lab ah: this.circle(ahPoint.x, ahPoint.y, labelFontSize/4 ).attr({stroke: 'none', fill: strokeColor}), lbl: this.text(labelPoint.x+10, labelPoint.y+10, symbol - ).attr({fill:'#000', 'font-size': labelFontSize}) + ).attr({fill:'#000', 'font-size': labelFontSize}), + name1: name1, + name2: name2 }; return ret; }; }; -// source: http://stackoverflow.com/questions/2627436/svg-animation-along-path-with-raphael +// Source: http://stackoverflow.com/questions/2627436/svg-animation-along-path-with-raphael Raphael.fn.circlePath = function(x , y, r) { return "M"+x+","+(y-r)+"A"+r+","+r+",0,1,1,"+(x-0.1)+","+(y-r)+" z"; }; -// nodes +// The nodes Raphael.fn.aNode = function(x, y, r, isFinal, hasSelfConn, - strokeWidth, strokeColor,labelText, labelFontSize) { + strokeWidth, strokeColor,labelText, labelFontSize, name) { var res = this.set(); // two circle element for dragging and binding connections var connector = this.circle(x, y, r).attr({stroke:0}).attr({fill: 'none'}); @@ -136,15 +138,20 @@ Raphael.fn.aNode = function(x, y, r, isFinal, hasSelfConn, if (isFinal) { res.push(ci); }; - return res; + return { + node: res, + name: name + }; }; -// source: http://www.davidcramer.net/code/63/dir-in-javascript.html +// Source: http://www.davidcramer.net/code/63/dir-in-javascript.html function dir(object) { methods = []; for (z in object) if (typeof(z) != 'number') methods.push(z); return methods.join(', '); }; + +// Drawing the graph. function graph() { var nodeRadius = 30; var nodeRadiusHi = nodeRadius + 10; @@ -162,8 +169,8 @@ function graph() { }, move = function (dx, dy) { this.moveTo(dx, dy); - for (var i = nodeById[this.id].length - 1; i >= 0; i--){ - nodeById[this.id][i].translate(dx, dy); + for (var i = nodeById[this.id].node.length - 1; i >= 0; i--){ + nodeById[this.id].node[i].translate(dx, dy); }; for (var i = connections.length; i--;) { r.connection(connections[i]); @@ -174,8 +181,9 @@ function graph() { this.animate({r:nodeRadius, opacity:nodeOpacity}, 500, ">"); }; // nodes - var nodes = []; - var nodeById = []; + var nodes = []; + var nodeById = []; + var graphNodeByName = []; var i = 0, n, color, isFinal, selfConn = false, selfConnSymbol; var nx = 30; var nxOffset = 100; @@ -204,11 +212,12 @@ function graph() { ny = ny + nyOffset; }; n = r.aNode(nx, ny, nodeRadius, isFinal, selfConn, - strokeWidth, strokeColor, symbol, labelFontSize); - n[1].attr({fill:color, opacity:nodeOpacity, cursor:'move'}); - n[1].drag(move, start, up); + strokeWidth, strokeColor, symbol, labelFontSize, state); + n.node[1].attr({fill:color, opacity:nodeOpacity, cursor:'move'}); + n.node[1].drag(move, start, up); nodes.push(n); - nodeById[n[1].id] = nodes[i]; + nodeById[n.node[1].id] = nodes[i]; + graphNodeByName[state] = n; nx = nx + nxOffset; i++; }; @@ -224,8 +233,9 @@ function graph() { if (state == statex) { continue; } else { - connections.push(r.connection(nodes[k][0], nodes[l][0], - strokeColor, strokeColor, strokeColor, ALPHABET[i], labelFontSize)); + connections.push(r.connection(nodes[k].node[0], nodes[l].node[0], + strokeColor, strokeColor, strokeColor, ALPHABET[i], labelFontSize, + nodes[k].name, nodes[l].name)); }; }; l++; @@ -239,6 +249,6 @@ function graph() { nodes: nodes, nodeById: nodeById, connections: connections, - test: 'test' + graphNodeByName: graphNodeByName }; }; diff --git a/javascripts/lib/raphael.js b/javascripts/lib/raphael.js index 222f87f..1afc473 100644 --- a/javascripts/lib/raphael.js +++ b/javascripts/lib/raphael.js @@ -3056,7 +3056,7 @@ Raphael = (function () { animationElements[element.id] && (params.start = animationElements[element.id].start); return this.animate(params, ms, easing, callback); }; - // hack! http://groups.google.com/group/raphaeljs/browse_thread/thread/f5dc3ea149d3540b + // Hack! http://groups.google.com/group/raphaeljs/browse_thread/thread/f5dc3ea149d3540b Element[proto].moveTo = function(x, y){ switch (this.type) { case "path": diff --git a/javascripts/uifunc.js b/javascripts/uifunc.js index fd608f5..24b7b09 100644 --- a/javascripts/uifunc.js +++ b/javascripts/uifunc.js @@ -1,5 +1,6 @@ -// enable/disable if input length is > 0 +// Enable/disable if input length is > 0. function checkLength(el, bId) { + if (graphit && (el.id == 'word')) return; if(el.value.length > 0) { enable(bId); } else { @@ -7,13 +8,44 @@ function checkLength(el, bId) { }; }; -// enable a disabled form item +// Enable/disable a enabled/disabled form item. function enable(id) { $(id).removeAttr("disabled"); }; -// disable a form item function disable(id) { $(id).attr("disabled","disabled"); }; -// call of RegexParser.parse() from UI +// UI messages. +function graph_inprogress() { + $('#checkMessage').html('...'); + $('#checkMessage').removeClass('success'); + $('#checkMessage').removeClass('failure'); + $('#word').removeClass('success'); + $('#word').removeClass('failure'); + $('#word').addClass('inprogress'); + window.g.mover.attr({fill: '#ffff88'}); +}; +function graph_success() { + $('#checkMessage').html('Word accepted'); + $('#checkMessage').removeClass('failure'); + $('#checkMessage').addClass('success'); + $('#word').removeClass('failure'); + $('#word').removeClass('inprogress'); + $('#word').addClass('success'); + $('#checkMessage').effect("highlight", {}, 1000); + window.g.mover.attr({fill: '#cdeb8b'}); +}; +function graph_failure() { + $('#checkMessage').html('Word not accepted'); + $('#checkMessage').removeClass('success'); + $('#checkMessage').addClass('failure'); + $('#word').removeClass('success'); + $('#word').removeClass('inprogress'); + $('#word').addClass('failure'); + $('#checkMessage').effect("highlight", {}, 1000); + window.g.mover.animate({fill: '#b02b2c'}, 250); +}; + +// Call of RegexParser.parse() from UI. function uiParse() { + disable('#graphit'); var parser = new RegexParser(); nfa = parser.parse($('#regex').attr('value')); $('#parseMessage').html('Parse: '+parser.getErrorMessage()); @@ -29,64 +61,151 @@ function uiParse() { enable('#word'); var dfa = new Nfa2Dfa(nfa); var ttable = dfa.do(); - window.g = graph(); disable('#regex'); disable('#parseButton'); + if(!graphit) return; + window.g = graph(); + disable('#checkButton'); }; $('#parseMessage').effect("highlight", {}, 1000); - s = 'abc'; - /*g.mover = g.paper.circle( - g.nodes[0][1][0].cx.baseVal.value, - g.nodes[0][1][0].cy.baseVal.value, 30 - ).attr({fill:'#00f', stroke: 'none', opacity: 0.5}); - for (var i=0; i < s.length; i++) { - (function(g, i) { - setTimeout(function() { - var state = g.nodes[i]; - var x = state[1][0].cx.baseVal.value; - var y = state[1][0].cy.baseVal.value; - g.mover.animateAlong( - g.paper.path( - 'M'+x+state[1][0].r.baseVal.value+','+y+' '+g.connections[i].line.attr('path').toString().substring(1) - +'L'+(g.nodes[i+1][1][0].cx.baseVal.value+(i*2))+','+g.nodes[i+1][1][0].cy.baseVal.value - ).attr({stroke:'none', 'stroke-width':4}) - , 2000) - }, 2000*i); - })(g, i); - };*/ + // preparing graph movements + window.gCurrentState = g.nodes[0]; + window.gPrevStates = []; + gPrevStates.push(gCurrentState); + window.inSameState = []; + window.failedInputs = []; + window.gPrevState = gCurrentState; + g.mover = g.paper.circle( + gCurrentState.node[1][0].cx.baseVal.value, + gCurrentState.node[1][0].cy.baseVal.value, 30 + ).attr({fill:'#ffff88', stroke: '#ccc', 'stroke-width': 4, opacity: 0.5}); + graph_inprogress(); + }; -// call of NfaSimulator.simulate() from UI +// Call of NfaSimulator.simulate() from UI. function uiSimulate() { var simulator = new NfaSimulator(nfa); var check = simulator.simulate($('#word').attr('value')); if (!check) { - $('#checkMessage').html('Word not accepted'); - $('#checkMessage').removeClass('success'); - $('#word').removeClass('success'); - $('#checkMessage').addClass('failure'); - $('#word').addClass('failure'); + graph_failure(); } else { - $('#checkMessage').html('Word accepted'); - $('#checkMessage').removeClass('failure'); - $('#word').removeClass('failure'); - $('#checkMessage').addClass('success'); - $('#word').addClass('success'); + graph_success(); }; - $('#checkMessage').effect("highlight", {}, 1000); }; - +// Filter input. function getKey(e, set) { var key = String.fromCharCode(e.which); + if (e.which == 13) return true; if (set.indexOf(key) >= 0 || e.which == 8) { return true; - } + }; return false; }; -// -function graphMove(symbol) { - true; +// Moving inside the graph by input symbols. +function graphMoveByInput(e) { + // no graph + if(!graphit) return; + + // we are at the beginning + if (!gCurrentState || !gPrevState) { + gPrevStates.push(g.nodes[0]); + gPrevState = g.nodes[0]; + gCurrentState = g.nodes[0]; + }; + + // backspace + if (e.which == 8) { + // step over failed inputs. + if (window.failedInputs.length > 0) { + if(window.failedInputs.length == 1) { + if (ttable[gCurrentState.name].isFinal) { + graph_success(); + } else { + graph_inprogress(); + }; + }; + window.failedInputs.pop(); + return; + } + + // no state change + if (window.inSameState.length > 0) { + var mx = g.mover.attr('cx'); + var my = g.mover.attr('cy'); + var ll = g.paper.path('M'+mx+','+my+' '+mx+','+(my-25)+'Z').attr({stroke: 0}); + g.mover.animateAlong(ll, 250, "bounce"); + window.inSameState.pop(); + if (ttable[window.gCurrentState.name].isFinal) { + graph_success(); + } else { + graph_inprogress(); + } + return; + } + + // go back one state + window.gPrevState = gPrevStates.pop(); + g.mover.animate( + {cx:gPrevState.node[1][0].cx.baseVal.value, cy:gPrevState.node[1][0].cy.baseVal.value}, + 750, "bounce" + ); + window.gCurrentState = gPrevState; + if (ttable[window.gCurrentState.name].isFinal) { + graph_success(); + } else { + graph_inprogress(); + }; + return; + }; + + // getting input + var key = String.fromCharCode(e.which); + var gNextState = g.graphNodeByName[window.ttable[window.gCurrentState.name][key]]; + + graph_inprogress(); + + // --> failure + if (!gNextState) { + window.failedInputs.push(true); + graph_failure(); + return; + }; + + // no state change + if(gNextState.name == gCurrentState.name) { + g.mover.animateAlong(gCurrentState.node[4], 500); + window.inSameState.push(true); + return; + } else { + // state change + var theConn; + for (var c in g.connections) { + if ((window.gCurrentState.name == g.connections[c].name1) && (gNextState.name == g.connections[c].name2)) { + theConn = g.connections[c]; + break; + }; + }; + var x = gCurrentState.node[1][0].cx.baseVal.value; + var y = gCurrentState.node[1][0].cy.baseVal.value; + line = g.paper.path( + 'M'+x+gCurrentState.node[1][0].r.baseVal.value+','+y+' '+theConn.line.attr('path').toString().substring(1) + +'L'+(gNextState.node[1][0].cx.baseVal.value)+','+gNextState.node[1][0].cy.baseVal.value + ).attr({stroke:'none'}); + (function(g, line) { + setTimeout(function() { + g.mover.animateAlong(line, 500) + return line; + }, 1); + })(g, line); + }; + window.gPrevStates.push(gCurrentState); + window.gPrevState = gCurrentState; + window.gCurrentState = gNextState; + if (ttable[window.gCurrentState.name].isFinal) { + graph_success(); + }; }; diff --git a/regexvis.html b/regexvis.html index a99b991..2b222d2 100644 --- a/regexvis.html +++ b/regexvis.html @@ -6,7 +6,7 @@ <meta http-equiv='Content-Language' content='en_EN' /> <meta name='author' content='Patrick Simianer' /> - <title>Visualizing Regular Expressions (Patrick Simianer/'Endliche Automaten' SS2010)</title> + <title>Visualizing Regular Expressions (Patrick Simianer, 'Endliche Automaten', SS2010)</title> <link rel='stylesheet' type='text/css' href='stylesheets/styles.css' /> @@ -44,40 +44,51 @@ <table> <tr> - <td style="text-align:right"><strong style="color:#303030">Alphabet:</strong></td> - <td><strong style="color:#303030"> + <td class="r"><strong class="grayc">Alphabet:</strong></td> + <td><strong class="grayc"> <script type="text/javascript" charset="utf-8"> document.write(ALPHABET.substr(0,ALPHABET.length-1)); - </script> - </strong> + </script></strong> </td> </tr> <tr> - <td style='text-align:right'><strong>Regular Expression:</strong></td> - <td><input type="text" name="regex" id="regex" value="" - onchange='checkLength(this, "#parseButton")' onkeypress="return getKey(event, ALPHABETS)" - onmouseout='checkLength(this, "#parseButton")' autocomplete="off" /> + <td class="r"><strong>Regular Expression:</strong></td> + <td><input type="text" name="regex" id="regex" value="" autocomplete="off" + onchange='checkLength(this, "#parseButton");' + onkeypress="return getKey(event, ALPHABETS);" + onmouseout='checkLength(this, "#parseButton");' /> <input type="button" name="parseButton" id="parseButton" - value="Parse" disabled='disabled' autocomplete="off" onclick="uiParse()" /> + value="Parse" disabled='disabled' autocomplete="off" onclick="uiParse();" /> </td> </tr> <tr> - <td style='text-align:right'><strong>Word:</strong></td> - <td><input type="text" name="word" id="word" value="" disabled='disabled' - onchange='checkLength(this, "#checkButton")' onkeypress="return getKey(event, ALPHABET)" - onmouseout='checkLength(this, "#checkButton")' autocomplete="off" /> + <td class="r"><strong>Word:</strong></td> + <td><input type="text" name="word" id="word" value="" disabled="disabled" autocomplete="off" + onchange='checkLength(this, "#checkButton");' + onkeypress="return graphMoveByInput(event, ALPHABET);" + onmouseout='checkLength(this, "#checkButton");' /> <input type="button" name="checkButton" id="checkButton" - value="Check" disabled='disabled' autocomplete="off" onclick="uiSimulate()" /> + value="Check" disabled='disabled' autocomplete="off" onclick="uiSimulate();" /> + </td> + </tr> + <tr> + <td class="r"><strong>Make a Graph:</strong></td> + <td> + <input type="checkbox" name="graphit" value="" id="graphit" autocomplete="off" + checked="checked" onchange="graphit = this.checked;" /> </td> </tr> </table> <p> - <a class='message gray' href='#' onclick="window.location.reload()"/>Reset</a> + <a class='message gray' href='#' onclick="window.location.reload();"/>Reload</a> <span id="parseMessage" class="message" style="display:none"></span> <span id="checkMessage" class="message" style="display:none"></span> </p> + + + <div id="holder"></div> </div> diff --git a/resources/urls.txt b/resources/urls.txt index 49d9c32..eed45e8 100644 --- a/resources/urls.txt +++ b/resources/urls.txt @@ -10,9 +10,7 @@ http://swtch.com/~rsc/regexp/regexp1.html http://www.gamedev.net/reference/articles/article2170.asp http://raphaeljs.com/reference.html - http://www.w3.org/TR/SVG/ - http://github.com/DmitryBaranovskiy/raphael/ http://groups.google.com/group/raphaeljs/browse_thread/thread/f7f7cffa82331503/3d40c581a0e6f50b?lnk=raot http://groups.google.com.au/group/raphaeljs/browse_thread/thread/c4bf09bd58546625/6de8fd1b07a65315?lnk=gst&q=graffle#6de8fd1b07a65315 diff --git a/stylesheets/styles.css b/stylesheets/styles.css index cac897b..cb22591 100644 --- a/stylesheets/styles.css +++ b/stylesheets/styles.css @@ -1,42 +1,42 @@ body { background-color: #fff; - font-family: Helvetica; - color: #000; + font-family: Helvetica; + color: #000; } - a { color: #000 } - input { vertical-align: bottom; - font-size: 1.1em; + font-size: 1.1em; } input[type='button'] { - vertical-align:middle; - font-size: 1.25em; - + vertical-align: middle; + font-size: 1.25em; } input[type='text'] { padding: 4px } + div#wrapper { - margin-left: auto; + margin-left: auto; margin-right: auto; - width: 1024px; - height: 100%; + width: 1024px; + height: 100%; } - div#holder { - margin-left: auto; + margin-left: auto; margin-right: auto; } +span#desc { display: none } -span#desc { display:none } .message { - font-weight: bold; + font-weight: bold; -moz-border-radius: 5px; - padding: 4px; - text-decoration: none; + padding: 4px; + text-decoration: none; } -.success { background-color: #CDEB8B } -.failure { background-color: #B02B2C } -.gray { background-color: #ccc } +.success { background-color: #cdeb8b } +.failure { background-color: #b02b2c } +.inprogress { background-color: #ffff88 } +.gray { background-color: #ccc } +.grayc { color: #ccc } +.r { text-align: right } |