Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
64 additions
and
1,141 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
Oops, something went wrong.