From 37d49b73d32799a8e1f65f5708ffcb47237ffb78 Mon Sep 17 00:00:00 2001 From: sbosse Date: Mon, 21 Jul 2025 23:18:02 +0200 Subject: [PATCH] Mon 21 Jul 22:43:21 CEST 2025 --- js/top/jamp.js | 534 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 js/top/jamp.js diff --git a/js/top/jamp.js b/js/top/jamp.js new file mode 100644 index 0000000..194858e --- /dev/null +++ b/js/top/jamp.js @@ -0,0 +1,534 @@ +/** + ** ============================== + ** 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]