/** ** ============================== ** O O O OOOO ** O O O O O O ** O O O O O O ** OOOO OOOO O OOO OOOO ** O O O O O O O ** O O O O O O O ** OOOO OOOO O O OOOO ** ============================== ** Dr. Stefan Bosse http://www.bsslab.de ** ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED ** BY THE AUTHOR(S). ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, ** MODIFIED, OR OTHERWISE USED IN A CONTEXT ** OUTSIDE OF THE SOFTWARE SYSTEM. ** ** $AUTHORS: Stefan Bosse ** $INITIAL: (C) 2006-2017 BSSLAB ** $CREATED: 20-11-17 by sbosse. ** $RCS: $Id: jamp.js,v 1.1 2020/02/03 09:45:01 sbosse Exp sbosse $ ** $VERSION: 1.2.2 ** ** $INFO: ** ** JAM Agent Monitor Port (AMP) interface program ** ** ** $ENDOFINFO */ global.config={simulation:false}; var onexit=false; var start=false; var Io = Require('com/io'); var Comp = Require('com/compat'); var Aios = Require('jam/aios'); var Esprima = Require('parser/estprima'); var Escodegen = Require('printer/estcodegen'); var Json = Require('jam/jsonfn'); var util = Require('util'); var Buf = Require('dos/buf'); var Net = Require('dos/network'); var CBL = Require('com/cbl'); var options = { debug:false, // This AMP port ip:'localhost', ip_port:10001, verbose:0, version:"1.2.2" } var out = console.log; var jamp = function(options) { var self=this; this.options=options; this.env=options.env||{}; this.verbose=options.verbose; this.ip=Aios.Chan.url2addr(options.ip,options.ip_port); this.classes={}; this.objects=[]; this.schedules=CBL(); this.commands=[]; this.events=[]; this.connected=none; this.out=function (msg) { out('[JAMP] '+msg); }; this.err=function (msg,err) { out('[JAMP] Error: '+msg); throw (err||'Program Error'); } this.warn=function (msg) { out('[JAMP] Warning: '+msg); } } // Import analyzer class... var JamAnal = Require('jam/analyzer'); JamAnal.current(Aios); jamp.prototype.analyzeSyntax=JamAnal.jamc.prototype.analyze; jamp.prototype.syntax=JamAnal.jamc.prototype.syntax; /** Add an agent class template {:} to the JAM world * */ jamp.prototype.addClass = function (name,constructor,env) { this.classes[name]=constructor; if (this.verbose) this.out('Agent class '+name+' added to library.'); this.objects.push( { name:name, fun:Aios.Code.minimize(Aios.Code.toString(constructor)), env:Aios.Code.minimize(Aios.Code.toString(env||{})), }); }; /** Analyze agent class template in text or object form * Returns {report:string,interface} */ jamp.prototype.analyze = function (ac,options) { var syntax,content,report,interface; if (Comp.obj.isString(ac)) { } else if (Comp.obj.isObject(ac)) { } else if (Comp.obj.isFunction(ac)) { content = 'var ac ='+ac; syntax = Esprima.parse(content, { tolerant: true, loc:true }); try { interface=this.analyzeSyntax(syntax,{ classname:options.classname||'anonymous', level:options.level==undefined?2:options.level, verbose:options.verbose, err:function (msg){throw msg}, out:function (msg){if (!report) report=msg; else report=report+'\n'+msg;}, warn:function (msg){if (!report) report=msg; else report=report+'\n'+msg;} }); return {report:report||'OK',interface:interface}; } catch (e) { return {report:e,interface:interface}; } } } /** Compile (analyze) a class constructor function and add it to the world class library */ jamp.prototype.compileClass = function (name,constructor,verbose) { var p,content,syntax,interface,text,env={},self=this,constr; content = 'var ac = '+constructor; syntax = Esprima.parse(content, { tolerant: true, loc:true }); interface = this.analyzeSyntax(syntax, { classname:name, level:2, verbose:verbose||0, err: function (msg){self.print(msg)}, out: function (msg){self.print(msg)}, warn: function (msg){self.print(msg)} }); // text=Json.stringify(template); for (p in interface.activities) env[p]=p; with (env) { eval('constr='+constructor) }; this.addClass(name,constr,env); } // Connect to remote node jamp.prototype.connect = function (to,callback) { var tokens=to.split(':'); if (!this.amp || this.amp.status(to)) { this.err('connect: Not connected: '+to); if (callback) callback(); return}; if (this.verbose) this.out('Connecting to '+to); if (callback) this.schedules.top(callback); if (tokens.length==2) this.amp.link(tokens[0],Number(tokens[1])); else this.amp.link(this.options.ip,Number(tokens[0])); } // Create agent process snapshot jamp.prototype.createAgent = function (cls,args) { var code,text; if (!this.classes[cls]) {this.err('createAgent: No such class '+cls); return}; try { code=Aios.Code.createAndReturn(this.classes[cls],cls,args,2); if (this.verbose) this.out('Created agent object '+code.process.agent.id+':'+cls+'('+util.inspect(args)+') ['+code.code.length+' bytes]'); this.objects.push(code.code); } catch (e) { this.err('createAgent failed: '+e); } } // Disconnect remote node endpoint jamp.prototype.disconnect = function (to,callback) { var tokens,self=this; if (!this.amp || !this.amp.snd.address) { if (callback) callback(); return}; if (callback) this.schedules.top(callback); if (!to) { this.amp.unlink(this.amp.snd.address,this.amp.snd.port,callback?this.schedules.next.bind(this.schedules):undefined); } else { tokens=to.split(':'); if (tokens.length==2) this.amp.unlink(tokens[0],Number(tokens[1]),callback?this.schedules.next.bind(this.schedules):undefined); else this.amp.unlink(this.options.ip,Number(tokens[0]),callback?this.schedules.next.bind(this.schedules):undefined); } } // Event handler jamp.prototype.emit = function (event,arg) { // console.log(event) if (this.events[event]) this.events[event](arg); } jamp.prototype.exit = function (stat) { process.exit(stat) } // Initialize jamp.prototype.init = function (callback) { var self=this; // Create AMP port this.amp = Aios.Chan.Amp({ rcv:this.ip, verbose:this.verbose }); this.amp.init(); this.amp.receiver(this.receiver.bind(this)); this.amp.start(callback); this.amp.on('route+',function (arg) { self.emit('route+',arg)}); this.on('route+',self.schedules.next.bind(self.schedules)); } // Event handler jamp.prototype.on = function (event,handler) { this.events[event]=handler; } // Parse command line arguments jamp.prototype.parse = function(argv) { var next,self=this,tokens,last,obj; argv=argv.slice(2); argv.forEach(function (arg) { switch (next) { case 'compile': case 'com': self.commands.push({ compile:arg }); next=undefined; break; case 'create': case 'cre': last={ create:arg, args:{} }; self.commands.push(last); next='arg'; break; case 'connect': case 'con': last={ connect:arg, }; self.commands.push(last); next=undefined; break; case '-ip': tokens=arg.split(':'); if (tokens.length==2) this.ip.address=tokens[0],this.ip.port=Number(tokens[1]); else this.ip.port=Number(tokens[0]); next=undefined; break; case 'arg': if (arg.charAt(0) == '{' || arg.charAt(0)=='[') { try{eval('obj='+arg)} catch (e) {}; last.args=obj;last=undefined;next=undefined; break; } else if (arg.indexOf(':')!=-1) { tokens=arg.split(':'); if (last) last.args[tokens[0]]=Comp.string.isNumeric(tokens[1])? Number(tokens[1]): Comp.string.isBoolean(tokens[1])? Boolean(tokens[1]):tokens[1]; break; } else {last=undefined;next=undefined; /*fall through */} default: switch (arg) { case '-v': self.verbose++; if (self.verbose>1) self.out('Increasing verbosity level to '+self.verbose); break; case '-h': case '-help': self.usage(true); break; case 'dup': case '2dup': case 'over': case 'swap': case 'drop': self.commands.push({stack:arg}); break; case 'dump': self.commands.push({dump:true}); break; case 'exit': case '.': self.commands.push({exit:true}); break; case 'disconnect': case 'dis': self.commands.push({disconnect:true}); break; case '-ip': case 'compile': case 'com': case 'create': case 'cre': case 'connect': case 'con': next=arg; break; case 'execute': case 'exe': self.commands.push({ request:'execute' }); break; case 'write': case 'wri': self.commands.push({ request:'write' }); break; case 'read': case 'rea': self.commands.push({ request:'read' }); break; default: self.err('Unknown command '+arg,true); } } }); }; /** Read agent templates from file and compile (analyze) agent class templates. * Expected file format: module.exports = { ac1: function (p1,p2,..) {}, ac2:.. } * */ jamp.prototype.readClass = function (file,options) { var self=this, ac, env, constr, interface, text, modu, p,m, regex1, ast=null, fileText=null, off=null; function errLoc(ast) { var err; if (ast && ast.errors && ast.errors.length) { err=ast.errors[0]; if (err.lineNumber != undefined) return 'line '+err.lineNumber; } return 'unknown' } try { if (!options) options={}; if (this.verbose>0) this.out('Looking up agent class template(s) from '+file); //modu=Require(file); if (Comp.obj.isEmpty(modu)) { if (this.verbose>0) this.out('Importing agent class template(s) from file '+file); if (Comp.string.get(file,0)!='/') file = (process.cwd?process.cwd()+'/':'./')+file; fileText=Io.read_file(file); ast=Esprima.parse(fileText, { tolerant: true, loc:true }); modu=require(file); } if (!modu) throw 'Empty module.'; for (m in modu) { ac=modu[m]; env={}; if (fileText) off=this.syntax.find(fileText,'VariableDeclarator',m); if (off && off.loc) this.syntax.offset=off.loc.start.line-1; content = 'var ac = '+ac; syntax = Esprima.parse(content, { tolerant: true, loc:true }); interface = this.analyzeSyntax(syntax, { classname:m, level:2, verbose:this.verbose||0, err: function (msg){self.err(msg)}, out: function (msg){self.out(msg)}, warn: function (msg){self.warn(msg)} }); // text=Json.stringify(ac); for (var p in interface.activities) env[p]=p; with (env) { eval('constr='+ac) }; if (this.verbose>0) this.out('Adding agent class constructor '+m+' ('+(typeof constr)+').'); this.addClass(m,constr,env); this.syntax.offset=0; } } catch (e) { this.out('Reading and parsing file "'+file+'" failed: '+e+', in '+errLoc(ast)); this.exit(); } }; // AMP message receiver handler jamp.prototype.receiver = function(handler) { // console.log(handler); switch (handler.cmd) { } } // Send request (agent,signal,class,info) to remote node endpoint jamp.prototype.request = function(op) { var self=this, obj = this.objects.pop(), buf=Buf.Buffer(); if (!this.amp) return; switch (op) { case 'execute': if (!Comp.obj.isString(obj)) return; Buf.buf_put_string(buf,obj); if (this.verbose) this.out('Sending request: '+op+' ['+obj.length+' bytes]'); this.schedules.push(function (next) { self.amp.request(Net.Command.PS_CREATE, buf, next); }); break; case 'write': if (!obj.name) return; Buf.buf_put_string(buf,obj.name); Buf.buf_put_string(buf,obj.fun); Buf.buf_put_string(buf,obj.env); if (this.verbose) this.out('Sending request: '+op+' ['+(obj.name.length+obj.fun.length+obj.env.length)+' bytes]'); this.schedules.push(function (next) { self.amp.request(Net.Command.PS_WRITE, buf, next); }); break; } } // Run the commands ... jamp.prototype.run = function() { var self=this,r,s; this.commands.forEach(function (cmd) { if (cmd.compile) self.readClass(cmd.compile); if (cmd.create) self.createAgent(cmd.create,cmd.args); if (cmd.request) self.request(cmd.request); if (cmd.disconnect) self.schedules.push(function (next) { self.disconnect(next)}); if (cmd.connect) self.schedules.push(function (next) { if (!self.status(cmd.connect)) { self.disconnect(); self.connect(cmd.connect/*,next()*/); } else next(); }); if (cmd.stack) { switch (cmd.stack) { case 'dup': r=self.objects.pop(); self.objects.push(r); self.objects.push(r); break; case '2dup': r=self.objects.pop(); s=self.objects.pop(); self.objects.push(s); self.objects.push(r); self.objects.push(s); self.objects.push(r); break; case 'drop': self.objects.pop(); break; case 'over': r=self.objects.pop(); s=self.objects.pop(); self.objects.push(s); self.objects.push(r); self.objects.push(s); break; case 'swap': r=self.objects.pop(); s=self.objects.pop(); self.objects.push(r); self.objects.push(s); break; } } if (cmd.dump) { console.log(self.objects)} if (cmd.exit) { self.schedules.push(function (next) {self.disconnect(undefined,function() {process.exit(0)});})} }); this.schedules.start(); } // Test connection status jamp.prototype.status = function(to) { to=Aios.Chan.url2addr(to); if (!this.amp) return false; return this.amp.status(to.address,to.port); } // Print usage message jamp.prototype.usage = function(exit) { var msg='JAM Agent Management Port Program, Version '+options.version+NL; msg += 'usage: jamp [commands]'+NL; msg += ' con[nect] \n .. connect to node'+NL; msg += ' dis[connect]\n .. disconnect last connected node'+NL; msg += ' com[pile]