Compare commits

...

14 Commits

Author SHA1 Message Date
Will Webberley
158b287116 disable transforms by default. Enable by calling enable() 2017-05-10 14:53:48 +01:00
Will Webberley
fd8686b305 updated gitignore 2017-05-09 19:34:59 +01:00
Will Webberley
5a1d655ce8 updated gitignore 2017-05-09 19:34:48 +01:00
Will Webberley
78708856eb removed un-needed console statements 2017-05-09 19:33:23 +01:00
flyingsparx
230a48e409 allow transform policies to actually create the appropriate transform on an instance 2017-04-09 23:10:27 +01:00
flyingsparx
fd7accb887 added the 'transform policy' policy to the core model 2017-04-09 22:19:18 +01:00
flyingsparx
f0c040b533 ensure sandboxed evaluations also work on client by directly caalling eval() 2017-04-09 22:08:41 +01:00
Will Webberley
839753a636 test cases and improvements for chained transforms 2017-04-09 21:50:49 +01:00
Will Webberley
a134e6ed60 allow transforms to reactively re-run if a dependency of the function is changed 2017-04-09 18:21:49 +01:00
Will Webberley
0112fcd205 added some test cases for transforms 2017-04-09 17:51:28 +01:00
Will Webberley
cb5a2befea safely evaluate transforms on the server 2017-04-09 16:05:46 +01:00
Will Webberley
19b309a29e added a transformer concept to the core model, allowing inherited concepts to use transforms 2017-04-09 14:33:12 +01:00
Will Webberley
e357ccea01 implemented a transform engine class to handle all transforms. called whenever instance updated 2017-04-09 14:20:35 +01:00
Will Webberley
97a0accfd1 added transform concept to core model and added basic functionality 2017-04-09 13:10:05 +01:00
8 changed files with 151 additions and 6 deletions

1
.gitignore vendored
View File

@ -18,3 +18,4 @@ dist/
*.ps
*.toc
*.gz
index.html

View File

@ -1,7 +1,9 @@
module.exports = [
"conceptualise an ~ entity ~ E",
"conceptualise a ~ transform ~ T that has the value W as ~ output ~ and has the value F as ~ transform function ~",
"conceptualise a ~ transformer ~ T that ~ uses ~ the transform U",
"conceptualise an ~ imageable thing ~ I that has the value V as ~ image ~",
"conceptualise a ~ timestamp ~ T that is an entity",
"conceptualise a ~ timestamp ~ T that is a transformer and has the value V as ~ date ~",
"conceptualise an ~ agent ~ A that is an entity and has the value V as ~ address ~",
"conceptualise an ~ individual ~ I that is an ~ agent ~",
"conceptualise a ~ card ~ C that is an entity and has the timestamp T as ~ timestamp ~ and has the value V as ~ content ~ and has the value W as ~ linked content ~ and has the value V as ~ number of keystrokes ~ and has the timestamp T as ~ start time ~ and has the value W as ~ submit time ~ and has the value L as ~ latitude ~ and has the value M as ~ longitude ~",
@ -21,5 +23,6 @@ module.exports = [
"conceptualise a ~ listen policy ~ P that is a policy",
"conceptualise a ~ listen onbehalfof policy ~ P that is a policy",
"conceptualise a ~ forwardall policy ~ P that is a policy and has the timestamp T as ~ start time ~ and has the value V as ~ all agents ~",
"conceptualise a ~ feedback policy ~ P that is a policy and has the value V as ~ acknowledgement ~"
"conceptualise a ~ feedback policy ~ P that is a policy and has the value V as ~ acknowledgement ~",
"conceptualise a ~ transform policy ~ P that is a policy and has the value V as ~ transform type ~ and ~ uses ~ the transform U"
];

View File

@ -158,6 +158,7 @@ class CEInstance {
if (propagate !== false) {
this.node.ruleEngine.enactRules(this, 'value', valueInstance, source);
}
this.node.transformEngine.enactTransforms(this, label, valueInstance, source);
}
return null;
}
@ -197,6 +198,7 @@ class CEInstance {
if (propagate !== false) {
this.node.ruleEngine.enactRules(this, 'relationship', relationshipInstance, source);
}
this.node.transformEngine.enactTransforms(this, label, relationshipInstance, source);
}
return null;
}

View File

@ -21,6 +21,7 @@ const CEParser = require('./CEParser.js');
const QuestionParser = require('./QuestionParser.js');
const NLParser = require('./NLParser.js');
const RuleEngine = require('./RuleEngine.js');
const TransformEngine = require('./TransformEngine.js');
class CENode {
@ -248,6 +249,7 @@ class CENode {
this.questionParser = new QuestionParser(this);
this.nlParser = new NLParser(this);
this.ruleEngine = new RuleEngine(this);
this.transformEngine = new TransformEngine(this);
this.concepts = [];
this.instances = [];
this.conceptDict = {};

View File

@ -151,7 +151,7 @@ class CEParser {
instance.sentences.push(t);
const remainder = t.replace(/^there is an? (?:[a-zA-Z0-9 ]*) named (?:[a-zA-Z0-9_]*|'[a-zA-Z0-9_ ]*') that/, '');
const facts = remainder.replace(/\band\b/g, '+').match(/(?:'(?:\\.|[^'])*'|[^+])+/g);
const facts = remainder.replace(/\band\b/g, '!+').match(/(?:'(?:\\.|[^'])*'|[^!+])+/g);
for (const fact of facts) {
this.processInstanceFact(instance, fact, source);
}
@ -189,7 +189,7 @@ class CEParser {
const tokens = t.split(' ');
tokens.splice(0, 1 + concept.name.split(' ').length + instanceName.split(' ').length);
const remainder = tokens.join(' ');
const facts = remainder.replace(/\band\b/g, '+').match(/(?:'(?:\\.|[^'])*'|[^+])+/g);
const facts = remainder.replace(/\band\b/g, '!+').match(/(?:'(?:\\.|[^'])*'|[^!+])+/g);
if (facts) {
for (const fact of facts) {
this.processInstanceFact(instance, fact, source);
@ -199,7 +199,7 @@ class CEParser {
}
processInstanceFact(instance, fact, source) {
const input = fact.trim().replace(/\+/g, 'and');
const input = fact.trim().replace(/!\+/g, 'and');
if (input.match(/^(?!has)([a-zA-Z0-9 ]*) the ([a-zA-Z0-9 ]*) ([a-zA-Z0-9_' ]*)/)) {
const re = /^(?!has)([a-zA-Z0-9 ]*) the ([a-zA-Z0-9 ]*) ([a-zA-Z0-9_' ]*)/;
const match = re.exec(input);

View File

@ -213,7 +213,17 @@ class PolicyHandler {
}
}
},
};
'transform policy': (policy) => {
if (policy.transform_type && policy.uses){
for (const instance of this.node.getInstances(policy.transform_type)) {
if (!instance.uses) {
instance.addRelationship('uses', policy.uses, true);
}
}
}
}
}
}
handle(policy) {

95
src/TransformEngine.js Normal file
View File

@ -0,0 +1,95 @@
/*
* Copyright 2017 W.M. Webberley & A.D. Preece (Cardiff University)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
'use strict';
class TransformEngine {
enable () {
this.enabled = true;
}
disable () {
this.enabled = false;
}
evaluate (instance, func) {
if (!this.enabled) {
return 'Transforms disabled';
}
if (func.indexOf('require') > -1){
return 'Invalid function';
}
try {
const sandbox = {
name: instance.name,
type: instance.concept.name
};
for (const attr of instance.values.concat(instance.relationships)) {
const value = attr.instance.name ? attr.instance.name : attr.instance;
sandbox[attr.label.toLowerCase().replace(/ /g, '_').replace(/'/g, '')] = value;
}
if (typeof window !== 'undefined' && window.document) {
return function () {
return eval(func);
}.call(sandbox);
}
else {
const vm = require('vm');
const script = new vm.Script(func);
const context = new vm.createContext(sandbox);
return script.runInContext(context, {timeout: 30});
}
}
catch (err){
return err;
}
}
enactTransforms(instance, label, targetInstance, source) {
let doTransform = false;
let transforms = [];
// Find out if the target property is a transform
if (targetInstance.concept && targetInstance.concept.name === 'transform'){
transforms = [targetInstance];
}
// Otherwise check if the 'label' is a dependency of any existing transforms on this instance
else {
const newLabel = 'this.' + label.toLowerCase().replace(/ /g, '_').replace(/'/g, '');
for (const rel of instance.relationships){
if (rel.instance.concept && rel.instance.concept.name === 'transform' && rel.instance.transform_function && rel.instance.transform_function.indexOf(newLabel) > -1){
transforms.push(rel.instance);
}
}
}
for (const transform of transforms) {
const e = this.evaluate(instance, transform.transform_function);
if (e){
instance.addValue(transform.output, e.toString(), true, source);
}
}
}
constructor(node) {
this.node = node;
this.enabled = false;
}
}
module.exports = TransformEngine;

View File

@ -150,6 +150,38 @@ describe('CEParser', function() {
expect(node.instances.betty_hughes.lives_in.name).to.be('Camden');
expect(node.instances.sally.lives_in.name).to.be('Kensington and Chelsea');
});
it('allow for transforms', () => {
node = new CENode(CEModels.core);
node.addCE('conceptualise a ~ person ~ P that is a transformer and has the value A as ~ first name ~ and has the value B as ~ last name ~ and has the value C as ~ full name ~');
node.addCE('there is a transform named t1 that has \'full name\' as output and has \'this.first_name + " " + this.last_name\' as transform function');
node.addCE('there is a person named p1 that has Jane as first name and has Smith as last name and uses the transform t1');
expect(node.instances.p1.full_name).to.be('Jane Smith');
node.addCE('the person p1 has Harry as first name');
expect(node.instances.p1.first_name).to.be('Harry');
expect(node.instances.p1.full_name).to.be('Harry Smith');
node.addCE('there is a transform named t2 that has date as output and has \'new Date(parseInt(this.name))\' as transform function');
node.addCE('there is a timestamp named \'715263762315\' that uses the transform t2');
expect(node.getInstanceByName('715263762315').date).to.contain('1992');
});
it('allow for transform chaining', () => {
const node = new CENode(CEModels.core);
node.addSentence("conceptualise a ~ person ~ P that is a transformer and has the value A as ~ first name ~ and has the value B as ~ last name ~ and has the value C as ~ full name ~ and has the value D as ~ username ~")
node.addSentence("there is a transform named t1 that has 'full name' as output and has 'this.first_name + \\' \\' + this.last_name' as transform function")
node.addSentence("there is a transform named t2 that has 'username' as output and has 'this.full_name.toLowerCase().replace(/ /g, \\'_\\')' as transform function");
node.addSentence("there is a person named p1 that has 'Jane' as first name and has 'Smith' as last name and uses the transform t1 and uses the transform t2");
expect(node.instances.p1.full_name).to.be('Jane Smith');
expect(node.instances.p1.username).to.be('jane_smith');
node.addSentence('the person p1 has \'Harry\' as first name');
expect(node.instances.p1.full_name).to.be('Harry Smith');
expect(node.instances.p1.username).to.be('harry_smith');
});
});
describe('Specific Examples', function() {