Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing api.js to get expression grammar working properly #76

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
url = git://github.com/slightlyoff/ometa-js.git
[submodule "third_party/mutation-summary"]
path = third_party/mutation-summary
url = https://code.google.com/p/mutation-summary/
url = https://github.com/rafaelw/mutation-summary.git
[submodule "third_party/benchmarkjs"]
path = third_party/benchmarkjs
url = https://github.com/bestiejs/benchmark.js.git
4 changes: 2 additions & 2 deletions bin/c.js

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions demos/parser/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="chrome=1">

<title>Quadrilateral demo - Cassowary Javascript</title>
<style type="text/css">
canvas { background-color: lightgray }
* { font-family: sans-serif}
</style>
</head>
<body>
<h1>Quadrilateral demo - Cassowary Javascript</h1>
<p>Below is the bounded quadrilateral demo reimplemented using expressions. <a href="../quad/quaddemo.html">See original implementation</a></p>
<canvas id="c" width="800" height="600"></canvas>
</body>

<script src='../../src/c.js'></script>
<script src='../../src/HashTable.js'></script>
<script src='../../src/HashSet.js'></script>
<script src='../../src/Error.js'></script>
<script src='../../src/SymbolicWeight.js'></script>
<script src='../../src/Strength.js'></script>
<script src='../../src/Variable.js'></script>
<script src='../../src/Point.js'></script>
<script src='../../src/Expression.js'></script>
<script src='../../src/Constraint.js'></script>
<script src='../../src/EditInfo.js'></script>
<script src='../../src/Tableau.js'></script>
<script src='../../src/SimplexSolver.js'></script>
<script src='../../src/Timer.js'></script>
<script src='../../src/parser/parser.js'></script>
<script src='../../src/parser/api.js'></script>

<script src='quadparser.js'></script>
</html>
172 changes: 172 additions & 0 deletions demos/parser/quadparser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*global c*/
/**
* [c1, c2, c3, c4] for corners starting from top left clockwise
* [m1, m2, m3, m4] for midpoints starting from top clockwise
*/
var expressions = [
// midpoints constrained in corners
'(c1x + c2x) / 2 == m1x', '(c1y + c2y) / 2 == m1y',
'(c2x + c3x) / 2 == m2x', '(c2y + c3y) / 2 == m2y',
'(c4x + c3x) / 2 == m3x', '(c4y + c3y) / 2 == m3y',
'(c1x + c4x) / 2 == m4x', '(c1y + c4y) / 2 == m4y',

// spaces between points
'c1x + 20 <= c2x', 'c1x + 20 <= c3x',
'c4x + 20 <= c2x', 'c4x + 20 <= c3x',
'c1y + 20 <= c3y', 'c1y + 20 <= c4y',
'c2y + 20 <= c3y', 'c2y + 20 <= c4y',

// contained inside canvas
'c1x >= 0', 'c2x >= 0', 'c3x >= 0', 'c4x >= 0',
'c1y >= 0', 'c2y >= 0', 'c3y >= 0', 'c4y >= 0',
'c1x <= 800', 'c2x <= 800', 'c3x <= 800', 'c4x <= 800',
'c1y <= 600', 'c2y <= 600', 'c3y <= 600', 'c4y <= 600',
].join(';');

var cOut = c(expressions);
var solver = c('solver');


/**
* Point - holds c.Variable coordinate representing a point
*/
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = 15;
}

// checks if a given coordinate is inside the box representing the point
contains(x, y) {
return (Math.abs(x - this.x.value) <= this.size/2 &&
Math.abs(y - this.y.value) <= this.size/2);
}

// draws a box representing the point
draw(ctx) {
var z = this.size;
ctx.strokeRect(this.x.value - z/2, this.y.value - z/2, z, z);
}

// sets stay value on the point
stay(x, y) {
this.x.value = x;
this.y.value = y;
solver.addStay(this.x).addStay(this.y);
}

// makes point coordinate variables editable
edit() {
return solver.addEditVar(this.x).addEditVar(this.y);
}

// suggests coordinate values for the point
suggest(x, y) {
return solver.suggestValue(this.x, x).suggestValue(this.y, y);
}
}



/**
* Application
*/
var App = {
init: function() {
this.canvas = document.getElementById('c');
this.cwidth = this.canvas.width;
this.cheight = this.canvas.height;
this._ctx = this.canvas.getContext('2d');
this._dragPoint = null;

// populating corners, midpoints
var ps = this.points = [];
var cs = this.corners = [];
var ms = this.midpoints = [];
var point;
for(var i=1; i<=4; i++) {
point = new Point(c(`c${i}x`), c(`c${i}y`));
cs.push(point);
ps.push(point);

point = new Point(c(`m${i}x`), c(`m${i}y`));
ms.push(point);
ps.push(point);
}

// set initial position
cs[0].stay(100, 100);
cs[1].stay(400, 100);
cs[2].stay(400, 400);
cs[3].stay(100, 400);
ms[0].stay(250, 100);
ms[1].stay(400, 250);
ms[2].stay(250, 400);
ms[3].stay(100, 250);

this.draw();

this._bindEvents();
},

draw: function() {
var g = this._ctx;
g.clearRect(0, 0, this.cwidth, this.cheight);
g.strokeStyle = 'black';

this.points.forEach(function(point) { point.draw(g); });

this._drawLine(this.midpoints);
this._drawLine(this.corners);
},

_drawLine: function(points) {
var g = this._ctx;

g.beginPath();
g.moveTo(points[0].x.value, points[0].y.value);
g.lineTo(points[1].x.value, points[1].y.value);
g.lineTo(points[2].x.value, points[2].y.value);
g.lineTo(points[3].x.value, points[3].y.value);
g.closePath();
g.stroke();
},

_bindEvents: function() {
this.canvas.addEventListener('mousedown', ev => this._mousedown(ev));
document.body.addEventListener('mousemove', ev => this._mousemove(ev));
document.body.addEventListener('mouseup', ev => this._mouseup(ev));
},

_mousedown: function(ev) {
var x = ev.pageX - this.canvas.offsetLeft;
var y = ev.pageY - this.canvas.offsetTop;

for(var i=0; i<this.points.length; i++) {
if(this.points[i].contains(x, y)) {
this._dragPoint = this.points[i];
this._dragPoint.edit().beginEdit();
document.body.style.cursor = 'move';
}
}
},

_mousemove: function(ev) {
if(!this._dragPoint) return;

var x = ev.pageX - this.canvas.offsetLeft;
var y = ev.pageY - this.canvas.offsetTop;
this._dragPoint.suggest(x, y).resolve();
this.draw();
},

_mouseup: function(ev) {
if(this._dragPoint) {
solver.endEdit();
document.body.style.cursor = '';
}
this._dragPoint = null;
}
};
App.init();
51 changes: 38 additions & 13 deletions src/parser/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,39 @@ var _c = function(expr) {
if (exprs[expr]) {
return exprs[expr];
}
var i;
switch(expr.type) {
case "Inequality":
var op = (expr.operator == "<=") ? c.LEQ : c.GEQ;
var i = new c.Inequality(_c(expr.left), op, _c(expr.right), weak);
var op = (expr.operator === "<=") ? c.LEQ : c.GEQ;
i = new c.Inequality(_c(expr.left), op, _c(expr.right), strong);
solver.addConstraint(i);
return i;
case "Equality":
var i = new c.Equation(_c(expr.left), _c(expr.right), weak);
i = new c.Equation(_c(expr.left), _c(expr.right), strong);
solver.addConstraint(i);
return i;
case "MultiplicativeExpression":
var i = c.times(_c(expr.left), _c(expr.right));
solver.addConstraint(i);
if (expr.operator === "/") {
i = c.divide(_c(expr.left), _c(expr.right));
} else {
i = c.times(_c(expr.left), _c(expr.right));
}
return i;
case "AdditiveExpression":
if (expr.operator == "+") {
return c.plus(_c(expr.left), _c(expr.right));
if (expr.operator === "+") {
i = c.plus(_c(expr.left), _c(expr.right));
} else {
return c.minus(_c(expr.left), _c(expr.right));
i = c.minus(_c(expr.left), _c(expr.right));
}
return i;
case "NumericLiteral":
return new c.Expression(expr.value);
case "Variable":
// console.log(expr);
// special variable to get the solver instance
if(expr.name === 'solver') {
return solver;
}

if(!vars[expr.name]) {
vars[expr.name] = new c.Variable({ name: expr.name });
}
Expand All @@ -59,12 +68,28 @@ var compile = function(expressions) {
// Global API entrypoint
c._api = function() {
var args = Array.prototype.slice.call(arguments);
if (args.length == 1) {
if(typeof args[0] == "string") {
var out = {};

if (args.length === 1) {
if(typeof args[0] === "string") {
// Parse and execute it
var r = c.parser.parse(args[0]);
return compile(r);
} else if(typeof args[0] == "function") {
out = compile(r);

// easy getters for solver instance and variables
// allows you to perform c('solver') and c('variableName')
if(out.length === 1 && (
out[0] instanceof c.SimplexSolver ||
out[0] instanceof c.Variable
)) { return out[0]; }

// attach solver and variable list
out.solver = solver;
out.vars = vars;

return out;

} else if(typeof args[0] === "function") {
solver._addCallback(args[0]);
}
}
Expand Down
13 changes: 13 additions & 0 deletions tests/parser/smoketest.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,18 @@ define([
});
});
});

it("can parse multiplication", function() {
expect(c("10*a==30")[0]).to.be.instanceOf(c.Constraint);
});

it("can parse multiple operators along with division", function() {
var constraint = c("x/2 - 1 == 29")[0];
expect(constraint).to.be.instanceOf(c.Constraint);
expect(constraint.expression.terms.size).to.equal(1);
constraint.expression.terms.each(function(cVar) {
expect(cVar.value).to.equal(60);
});
});
});
});