summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--javascripts/globals.js3
-rw-r--r--javascripts/graph.js48
-rw-r--r--javascripts/lib/raphael.js2
-rw-r--r--javascripts/uifunc.js203
-rw-r--r--regexvis.html43
-rw-r--r--resources/urls.txt2
-rw-r--r--stylesheets/styles.css40
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 }