Skip to content

Commit

Permalink
Version 0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
ajlopez committed Sep 5, 2014
1 parent c1efdba commit ef09074
Show file tree
Hide file tree
Showing 14 changed files with 64 additions and 1,141 deletions.
7 changes: 6 additions & 1 deletion README.md
@@ -1,6 +1,6 @@
# SimpleRules

Rule engine implementation using Rete-like algorithm.
Simple Rule engine implementation in JavaScript.

## Installation

Expand All @@ -20,6 +20,7 @@ var simplerules = require('simplerules');
```

TBD

## Development

```
Expand All @@ -37,6 +38,10 @@ TBD

- Samples

## Versions

- 0.0.1 Published

## Contribution

Feel free to [file issues](https://github.com/ajlopez/SimplGo) and submit
Expand Down
210 changes: 3 additions & 207 deletions lib/simplerules.js
@@ -1,212 +1,8 @@

'use strict';

var simplerules = (function () {
var TokenType = { Name: 1 };
var engine = require('./engine');

function Token(value, type) {
this.value = value;
this.type = type;
}

function Lexer(text) {
var length = text ? text.length : 0;
var position = 0;

this.nextToken = function () {
skipSpaces();

var ch = nextChar();

if (ch === null)
return null;

if (isFirstCharOfName(ch))
return nextName(ch);

if (isDigit(ch))
return nextInteger(ch);
}

function nextName(ch) {
var value = ch;

for (ch = nextChar(); ch && isCharOfName(ch); ch = nextChar())
value += ch;

if (ch)
pushChar(ch);

return new Token(value, TokenType.Name);
}

function nextInteger(ch) {
var value = ch;

for (ch = nextChar(); ch && isDigit(ch); ch = nextChar())
value += ch;

if (ch)
pushChar(ch);

return new Token(value, TokenType.Integer);
}

function nextChar() {
if (position >= length)
return null;

return text[position++];
}

function skipSpaces() {
for (var ch = nextChar(); ch && isSpace(ch);)
ch = nextChar();

if (ch)
pushChar(ch);
}

function pushChar(ch) {
if (ch)
position--;
}

function isFirstCharOfName(ch) {
return isLetter(ch) || ch === '_';
}

function isCharOfName(ch) {
return isLetter(ch) || isDigit(ch) || ch === '_';
}

function isDigit(ch) {
return ch >= '0' && ch <= '9';
}

function isLetter(ch) {
return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z';
}

function isSpace(ch) {
return ch <= ' ' && ch !== '\n' && ch !== '\r';
}
};

function Item(obj) {
this.listeners = { };
this.object = obj;
}

Item.prototype.setProperty = function (name, value) {
if (this.object[name] === value)
return;

if (value === undefined)
delete this.object[name];
else
this.object[name] = value;

if (this.listeners[name]) {
var listeners = this.listeners[name];
for (var k in listeners)
listeners[k](name, value);
}
};

Item.prototype.getProperty = function (name, value) {
return this.object[name];
};

Item.prototype.onSetProperty = function (name, listener) {
if (!this.listeners[name])
this.listeners[name] = [];
this.listeners[name].push(listener);
};

Item.prototype.isDefined = function (name) {
return this.object[name] !== undefined;
};

function RuleStep(engine, state) {
var self = this;
var fired = false;
var backtrack = null;

this.makeTask = function () {
return function () {
self.run();
self.firstRun(state);
};
};

this.run = function () {
if (this.check(state)) {
if (!fired) {
fired = true;
backtrack = this.makeBacktrack(state);
var next = this.makeNextTask(state);
if (next)
engine.addTask(next);
}
}
else if (fired) {
fired = false;
if (backtrack)
backtrack();
backtrack = null;
}
};

// Default behavior

this.check = function (state) { return false; }
this.makeBacktrack = function (state) { return null; }
this.makeNextTask = function (state) { return null; }
}

function Engine() {
var onaddobj = [];
var tasks = [];

this.addObject = function (obj) {
var item = new Item(obj);

for (var k in onaddobj)
onaddobj[k](item);

return item;
};

this.onAddObject = function (listener) {
onaddobj.push(listener);
};

this.addTask = function (task) {
tasks.push(task);
};

this.addRule = function (rule) {
rule.start(this);
};

this.createStep = function (state) {
return new RuleStep(this, state);
};

this.runSync = function () {
while (tasks.length)
tasks.pop()();
};
};

return {
Lexer: Lexer,
TokenType: TokenType,
createEngine: function () { return new Engine(); }
};
})();

if (typeof window === 'undefined') {
module.exports = simplerules;
module.exports = {
engine: engine
}
9 changes: 5 additions & 4 deletions package.json
@@ -1,16 +1,17 @@
{ "name": "simplerules"
, "description": "Rule engine, using Rete-like algorithm, compiling to JavaScript"
, "description": "Simple rule engine in JavaScript"
, "keywords": [ "ruleengine", "compiler", "javascript" ]
, "version": "0.0.1alpha"
, "version": "0.0.1"
, "author": "Angel 'Java' Lopez <webmaster@ajlopez.com> (http://www.ajlopez.com)"
, "repository": { "type": "git", "url": "git://github.com/ajlopez/SimpleRules.git" }
, "main": "./lib/simplerules.js"
, "engines": { "node": ">= 0.6.0 && < 0.9.0" }
, "engines": { "node": ">= 0.6.0 && < 0.13.0" }
, "scripts": {
"test": "node ./test.js"
"test": "simpleunit test"
}
, "dependencies": {
}
, "devDependencies": {
"simpleunit": "0.0.4"
}
}
106 changes: 50 additions & 56 deletions test/engine.js
@@ -1,61 +1,55 @@

var simplerules = require('../'),
assert = require('assert');

// createEngine is defined

assert.ok(simplerules.createEngine);
assert.equal(typeof simplerules.createEngine, 'function');

var engine = simplerules.createEngine();
assert.ok(engine);

var obj = { };
var item = engine.addObject(obj);

// Is defined

assert.equal(item.isDefined('name'), false);

// On set property

var value = 0;

item.onSetProperty('name', function () { value = 1; });
item.setProperty('name', 'Adam');
assert.equal(value, 1);
assert.equal(obj.name, 'Adam');

// Get property

assert.equal(item.getProperty('name'), 'Adam');

// On add object

var value = null;

engine.onAddObject(function (item) { value = item.object; });
engine.addObject(4);

assert.equal(value, 4);
var engine = require('..').engine;

// Add task and run sync

var value = null;

engine.addTask(function () { value = 1; });
engine.runSync();
assert.equal(value, 1);

// Add tasks

var value = 0;

function makeTask(n) { return function() { value += n; }; };
exports['create engine'] = function (test) {
var eng = engine({ name: 'engine1', title: 'Engine 1' });

test.ok(eng);
test.equal(eng.name, 'engine1');
test.equal(eng.title, 'Engine 1');
}

for (var k = 1; k <= 3; k++)
engine.addTask(makeTask(k));
exports['add rule'] = function (test) {
var eng = engine({});

var rule = eng.rule({ name: 'rule1', title: 'Rule 1' });

test.ok(rule);
test.equal(rule.name, 'rule1');
test.equal(rule.title, 'Rule 1');
}

engine.runSync();
exports['add and run rule on model'] = function (test) {
var model = { temperature: 37 };

var eng = engine({});

eng.rule({ name: 'rule1', title: 'Rule 1' })
.condition({ name: 'temperature', value: 37 })
.action({ set: 'hasFever', value: true });

eng.run(model);

test.equal(model.temperature, 37);
test.equal(model.hasFever, true);
}

assert.equal(value, 6);
exports['two rules and run on model'] = function (test) {
var model = { temperature: 40 };

var eng = engine({});

eng.rule({ name: 'rule1', title: 'Rule 1' })
.condition({ name: 'hasFever', value: true })
.action({ set: 'inBed', value: true });

eng.rule({ name: 'rule2', title: 'Rule 2' })
.condition({ name: 'temperature', value: 37, operator: '>=' })
.action({ set: 'hasFever', value: true });

eng.run(model);

test.equal(model.temperature, 40);
test.equal(model.hasFever, true);
test.equal(model.inBed, true);
}

0 comments on commit ef09074

Please sign in to comment.