/** * 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. * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. */ package org.thingml.compilers.javascript; import org.sintef.thingml.*; import org.sintef.thingml.constraints.ThingMLHelpers; import org.sintef.thingml.helpers.*; import org.thingml.compilers.Context; import org.thingml.compilers.DebugProfile; import org.thingml.compilers.thing.common.FSMBasedThingImplCompiler; import java.util.List; import java.util.Map; /** * Created by bmori on 16.04.2015. */ public class JSThingImplCompiler extends FSMBasedThingImplCompiler { DebugProfile debugProfile; @Override public void generateImplementation(Thing thing, Context ctx) { debugProfile = ctx.getCompiler().getDebugProfiles().get(thing); final StringBuilder builder = ctx.getBuilder(ctx.firstToUpper(thing.getName()) + ".js"); builder.append("'use strict';\n\n"); if (ctx.getContextAnnotation("hasEnum") != null && ctx.getContextAnnotation("hasEnum").equals("true")) { builder.append("const Enum = require('./enums');\n"); } builder.append("const StateJS = require('state.js');\n"); builder.append("const EventEmitter = require('events').EventEmitter;\n"); builder.append("StateJS.internalTransitionsTriggerCompletion = true;\n"); if (debugProfile.isActive()) { generatePrintDebugFunction(thing, builder, ctx); } if (thing.getStreams().size() > 0) { builder.append("const Rx = require('rx'),\n"); } if(((JSCompiler)ctx.getCompiler()).multiThreaded) { builder.append("var instance = undefined;\n"); builder.append("process.on('message', (m) => {\n"); builder.append("switch (m.lc) {\n"); builder.append("case 'new':\n"); builder.append("instance = new " + ctx.firstToUpper(thing.getName()) + "(m.name, null"); for (Property p : ThingHelper.allUsedProperties(thing)) { builder.append(", m." + p.getName()); } builder.append(", false);\n"); builder.append("break;\n"); builder.append("case 'init':\n"); builder.append("instance._init();\n"); builder.append("break;\n"); builder.append("case 'stop':\n"); builder.append("instance._stop();\n"); builder.append("break;\n"); builder.append("case 'set':\n"); builder.append("switch (m.property) {\n"); for (Property p : ThingHelper.allUsedProperties(thing)) { builder.append("case '" + p.getName() + "': "); builder.append("instance." + ThingMLElementHelper.qname(p, "_") + "_var = m.value;\n"); builder.append("break;\n"); } builder.append("default: break;\n"); builder.append("}\n"); builder.append("break;\n"); builder.append("case 'delete':\n"); builder.append("instance._delete();\n"); builder.append("break;\n"); builder.append("default:\n"); builder.append("instance._receive(m);\n"); builder.append("break;\n"); builder.append("}"); builder.append("});\n"); } builder.append("\n/**\n"); builder.append(" * Definition for type : " + thing.getName() + "\n"); builder.append(" **/\n"); builder.append("function " + ctx.firstToUpper(thing.getName()) + "(name, root"); for (Property p : ThingHelper.allUsedProperties(thing)) { if (!AnnotatedElementHelper.isDefined(p, "private", "true") && p.eContainer() instanceof Thing) { builder.append(", "); builder.append(ThingMLElementHelper.qname(p, "_") + "_var"); } }//TODO: changeable properties? builder.append(", debug) {\n\n"); builder.append("this.name = name;\n"); builder.append("this.root = (root === null)? this : root;\n"); builder.append("this.debug = debug;\n"); builder.append("this.ready = false;\n"); builder.append("this.bus = new EventEmitter();\n"); if(ThingHelper.hasSession(thing)) { builder.append("//Children\n"); builder.append("this.forkID = 0;\n"); builder.append("this.forks = [];\n"); } builder.append("//Attributes\n"); for (Property p : ThingHelper.allUsedProperties(thing)) { if (AnnotatedElementHelper.isDefined(p, "private", "true") || !(p.eContainer() instanceof Thing)) { builder.append("this."); builder.append(ThingMLElementHelper.qname(p, "_") + "_var"); Expression initExp = ThingHelper.initExpression(thing, p); if (initExp != null) { builder.append(" = "); ctx.getCompiler().getThingActionCompiler().generate(initExp, builder, ctx); } //TODO: Init builder.append(";\n"); } else { builder.append("this." + ThingMLElementHelper.qname(p, "_") + "_var" + " = " + ThingMLElementHelper.qname(p, "_") + "_var" + ";\n"); builder.append("this.debug_" + ThingMLElementHelper.qname(p, "_") + "_var" + " = " + ThingMLElementHelper.qname(p, "_") + "_var" + ";\n"); } }//TODO: public/private properties? if (thing.getStreams().size() > 0) { builder.append("this.eventEmitterForStream = new EventEmitter();\n"); } builder.append("this.build(name, root);\n"); builder.append("}\n"); if (thing.getStreams().size() > 0) { builder.append("//CEP dispatch functions\n"); builder.append(ctx.firstToUpper(thing.getName()) + ".prototype.cepDispatch = function(message) {\n"); for (Stream s : thing.getStreams()) { for (SimpleSource simpleSource : ThingMLHelpers.allSimpleSources(s.getInput())) { ReceiveMessage rm = simpleSource.getMessage(); builder.append("if( message._port === '" + rm.getPort().getName() + "' && message._msg === '" + rm.getMessage().getName() + "') {\n") .append("\tthis.eventEmitterForStream.emit('" + ThingMLElementHelper.qname(simpleSource, "_") + "',message);\n") .append("}\n"); } } builder.append("}\n"); } builder.append("//State machine (states and regions)\n"); builder.append(ctx.firstToUpper(thing.getName()) + ".prototype.build = function(session, root) {//optional session name and root instance to fork a new session\n"); if(ThingHelper.hasSession(thing)) { builder.append("if (root === null || root == undefined) {//building root component\n"); } for (StateMachine b : ThingMLHelpers.allStateMachines(thing)) { ((FSMBasedThingImplCompiler) ctx.getCompiler().getThingImplCompiler()).generateState(b, builder, ctx); } if(ThingHelper.hasSession(thing)) { builder.append("}\n"); } for (StateMachine b : ThingMLHelpers.allStateMachines(thing)) { ctx.addContextAnnotation("session", "true"); for (Session s : CompositeStateHelper.allContainedSessions(b)) {//FIXME: lots of code duplication here..... builder.append("else if(session === '" + s.getName() + "') {//building session " + s.getName() + "\n"); builder.append("this.root = root;\n"); builder.append("root.forkID = root.forkID + 1;\n"); builder.append("this.forkID = root.forkID;\n"); builder.append("this.statemachine = new StateJS.StateMachine('" + s.getName() + "')"); generateActionsForState(s, builder, ctx); builder.append(";\n"); ctx.addContextAnnotation("container", "this." + ThingMLElementHelper.qname(ThingMLHelpers.findContainingRegion(s), "_")); builder.append("let " + ThingMLElementHelper.qname(s, "_") + "_session = new StateJS.Region('" + s.getName() + "', this.statemachine);\n"); builder.append("let _initial_" + ThingMLElementHelper.qname(s, "_") + "_session = new StateJS.PseudoState('_initial', " + ThingMLElementHelper.qname(s, "_") + "_session, StateJS.PseudoStateKind.Initial);\n"); for (State st : s.getSubstate()) { ctx.addContextAnnotation("container", ThingMLElementHelper.qname(s, "_") + "_session"); ((FSMBasedThingImplCompiler) ctx.getCompiler().getThingImplCompiler()).generateState(st, builder, ctx); } builder.append("_initial_" + ThingMLElementHelper.qname(s, "_") + "_session.to(" + ThingMLElementHelper.qname(s.getInitial(), "_") + ");\n"); for (Handler h : StateHelper.allEmptyHandlers(s)) { generateHandler(h, null, null, builder, ctx); } final Map<Port, Map<Message, List<Handler>>> allHanders = StateHelper.allMessageHandlers(s); for (Map.Entry<Port, Map<Message, List<Handler>>> entry : allHanders.entrySet()) { final Port p = entry.getKey(); final Map<Message, List<Handler>> map = entry.getValue(); for (Map.Entry<Message, List<Handler>> entry2 : map.entrySet()) { final List<Handler> handlers = entry2.getValue(); final Message m = entry2.getKey(); for (Handler h : handlers) { generateHandler(h, m, p, builder, ctx); } } } builder.append("}\n"); } ctx.removeContextAnnotation("session"); } for (Stream stream : thing.getStreams()) { ctx.getCompiler().getCepCompiler().generateStream(stream, builder, ctx); } builder.append("}\n"); ctx.getCompiler().getThingApiCompiler().generatePublicAPI(thing, ctx); builder.append(ctx.firstToUpper(thing.getName()) + ".prototype.toString = function() {\n"); builder.append("let result = 'instance ' + this.name + ':' + this.constructor.name + '\\n';\n"); for (Property p : ThingHelper.allUsedProperties(thing)) { builder.append("result += '\\n\\t" + p.getName() + " = ' + this." + ctx.getVariableName(p) + ";\n"); } builder.append("result += '';\n"); builder.append("return result;\n"); builder.append("};\n"); builder.append("module.exports = " + ctx.firstToUpper(thing.getName()) + ";\n"); } protected void generateStateMachine(StateMachine sm, StringBuilder builder, Context ctx) { builder.append("this.statemachine = new StateJS.StateMachine('" + sm.getName() + "')"); generateActionsForState(sm, builder, ctx); builder.append(";\n"); if (sm.isHistory()) builder.append("this._initial_" + ThingMLElementHelper.qname(sm, "_") + " = new StateJS.PseudoState('_initial', this.statemachine, StateJS.PseudoStateKind.ShallowHistory);\n"); else builder.append("this._initial_" + ThingMLElementHelper.qname(sm, "_") + " = new StateJS.PseudoState('_initial', this.statemachine, StateJS.PseudoStateKind.Initial);\n"); for (Region r : sm.getRegion()) { if (!(r instanceof Session) && !(r instanceof CompositeState)) { ctx.addContextAnnotation("container", "this.statemachine"); generateRegion(r, builder, ctx); } } for (State s : sm.getSubstate()) { if (!(s instanceof Session)) { ctx.addContextAnnotation("container", "this.statemachine"); generateState(s, builder, ctx); } } builder.append("this._initial_" + ThingMLElementHelper.qname(sm, "_") + ".to(" + ThingMLElementHelper.qname(sm.getInitial(), "_") + ");\n"); for (Handler h : StateHelper.allEmptyHandlers(sm)) { generateHandler(h, null, null, builder, ctx); } //TODO: we should revise some derived properties, not so nice to use in Java... final Map<Port, Map<Message, List<Handler>>> allHanders = StateHelper.allMessageHandlers(sm); for (Map.Entry<Port, Map<Message, List<Handler>>> entry : allHanders.entrySet()) { final Port p = entry.getKey(); final Map<Message, List<Handler>> map = entry.getValue(); for (Map.Entry<Message, List<Handler>> entry2 : map.entrySet()) { final List<Handler> handlers = entry2.getValue(); final Message m = entry2.getKey(); for (Handler h : handlers) { generateHandler(h, m, p, builder, ctx); } } } } private void generateActionsForState(State s, StringBuilder builder, Context ctx) { if (s.getEntry() != null || debugProfile.isDebugBehavior() || s instanceof FinalState) { builder.append(".entry(() => {\n"); if (debugProfile.isDebugBehavior()) { builder.append("" + ThingMLHelpers.findContainingThing(s).getName() + "_print_debug(this, '" + ctx.traceOnEntry(ThingMLHelpers.findContainingThing(s), ThingMLHelpers.findContainingRegion(s), s) + "');\n"); } if (s.getEntry() != null) ctx.getCompiler().getThingActionCompiler().generate(s.getEntry(), builder, ctx); if (s instanceof FinalState) { builder.append("setImmediate(()=>this._stop());\n"); } builder.append("})"); } if (s.getExit() != null || debugProfile.isDebugBehavior()) { builder.append(".exit(() => {\n"); if (debugProfile.isDebugBehavior()) { builder.append("" + ThingMLHelpers.findContainingThing(s).getName() + "_print_debug(this, '" + ctx.traceOnExit(ThingMLHelpers.findContainingThing(s), ThingMLHelpers.findContainingRegion(s), s) + "');\n"); } if (s.getExit() != null) ctx.getCompiler().getThingActionCompiler().generate(s.getExit(), builder, ctx); builder.append("})"); } /*if (s.getEntry() != null || s.getExit() != null || debugProfile.isDebugBehavior()) { builder.append("\n\n"); }*/ } protected void generateCompositeState(CompositeState c, StringBuilder builder, Context ctx) { String containerName = ctx.getContextAnnotation("container"); if (CompositeStateHelper.hasSeveralRegions(c)) { builder.append("let " + ThingMLElementHelper.qname(c, "_") + " = new StateJS.State('" + c.getName() + "', " + containerName + ")\n"); generateActionsForState(c, builder, ctx); builder.append(";\n"); for (Region r : c.getRegion()) { ctx.addContextAnnotation("container", ThingMLElementHelper.qname(c, "_")); generateRegion(r, builder, ctx); } for (State s : c.getSubstate()) { ctx.addContextAnnotation("container", ThingMLElementHelper.qname(c, "_")/* + "_default"*/); generateState(s, builder, ctx); } } else { builder.append("let " + ThingMLElementHelper.qname(c, "_") + " = new StateJS.State('" + c.getName() + "', " + containerName + ")"); generateActionsForState(c, builder, ctx); builder.append(";\n"); for (State s : c.getSubstate()) { ctx.addContextAnnotation("container", ThingMLElementHelper.qname(c, "_")); generateState(s, builder, ctx); } } if (c.isHistory()) builder.append("let _initial_" + ThingMLElementHelper.qname(c, "_") + " = new StateJS.PseudoState('_initial', " + ThingMLElementHelper.qname(c, "_") + ", StateJS.PseudoStateKind.ShallowHistory);\n"); else builder.append("let _initial_" + ThingMLElementHelper.qname(c, "_") + " = new StateJS.PseudoState('_initial', " + ThingMLElementHelper.qname(c, "_") + ", StateJS.PseudoStateKind.Initial);\n"); builder.append("_initial_" + ThingMLElementHelper.qname(c, "_") + ".to(" + ThingMLElementHelper.qname(c.getInitial(), "_") + ");\n"); } @Override protected void generateFinalState(FinalState s, StringBuilder builder, Context ctx) { generateAtomicState(s, builder, ctx); } protected void generateAtomicState(State s, StringBuilder builder, Context ctx) { String containerName = ctx.getContextAnnotation("container"); if (s instanceof FinalState) { builder.append("let " + ThingMLElementHelper.qname(s, "_") + " = new StateJS.FinalState('" + s.getName() + "', " + containerName + ")"); } else { builder.append("let " + ThingMLElementHelper.qname(s, "_") + " = new StateJS.State('" + s.getName() + "', " + containerName + ")"); } generateActionsForState(s, builder, ctx); builder.append(";\n"); } public void generateRegion(Region r, StringBuilder builder, Context ctx) { String containerName = ctx.getContextAnnotation("container"); builder.append("let " + ThingMLElementHelper.qname(r, "_") + "_reg = new StateJS.Region('" + r.getName() + "', " + containerName + ");\n"); if (r.isHistory()) builder.append("let _initial_" + ThingMLElementHelper.qname(r, "_") + "_reg = new StateJS.PseudoState('_initial', " + ThingMLElementHelper.qname(r, "_") + "_reg, StateJS.PseudoStateKind.ShallowHistory);\n"); else builder.append("let _initial_" + ThingMLElementHelper.qname(r, "_") + "_reg = new StateJS.PseudoState('_initial', " + ThingMLElementHelper.qname(r, "_") + "_reg, StateJS.PseudoStateKind.Initial);\n"); for (State s : r.getSubstate()) { ctx.addContextAnnotation("container", ThingMLElementHelper.qname(r, "_") + "_reg"); generateState(s, builder, ctx); } builder.append("_initial_" + ThingMLElementHelper.qname(r, "_") + "_reg.to(" + ThingMLElementHelper.qname(r.getInitial(), "_") + ");\n"); } //FIXME: avoid duplication in the following 3 methods!!! private void generateHandlerAction(Handler h, Message m, StringBuilder builder, Context ctx, String debug) { if (h.getEvent().size() == 0) { builder.append(".effect((message) => {\n"); builder.append(debug); ctx.getCompiler().getThingActionCompiler().generate(h.getAction(), builder, ctx); builder.append("})\n\n"); } else { builder.append(".effect((" + m.getName() + ") => {\n"); builder.append(debug); ctx.getCompiler().getThingActionCompiler().generate(h.getAction(), builder, ctx); builder.append("})"); } } protected void generateTransition(Transition t, Message msg, Port p, StringBuilder builder, Context ctx) { if (t.getEvent().size() == 0) { builder.append(ThingMLElementHelper.qname(t.getSource(), "_") + ".to(" + ThingMLElementHelper.qname(t.getTarget(), "_") + ")"); if (t.getGuard() != null) { builder.append(".when((message) => {"); builder.append(" return "); ctx.getCompiler().getThingActionCompiler().generate(t.getGuard(), builder, ctx); builder.append(";})"); } } else { builder.append(ThingMLElementHelper.qname(t.getSource(), "_") + ".to(" + ThingMLElementHelper.qname(t.getTarget(), "_") + ")"); builder.append(".when((" + msg.getName() + ") => {"); builder.append("return " + msg.getName() + "._port === '" + p.getName() + "' && " + msg.getName() + "._msg === '" + msg.getName() + "'"); if (t.getGuard() != null) { builder.append(" && "); ctx.getCompiler().getThingActionCompiler().generate(t.getGuard(), builder, ctx); } builder.append(";})"); } if (t.getAction() != null) { String debugString = ""; if (debugProfile.isDebugBehavior()) debugString = "" + ThingMLHelpers.findContainingThing(t).getName() + "_print_debug(this, '" + ctx.traceTransition(ThingMLHelpers.findContainingThing(t), t, p, msg) + "');\n"; generateHandlerAction(t, msg, builder, ctx, debugString); } else { if (debugProfile.isDebugBehavior()) { builder.append(".effect(() => {\n"); builder.append("" + ThingMLHelpers.findContainingThing(t).getName() + "_print_debug(this, '" + ctx.traceTransition(ThingMLHelpers.findContainingThing(t), t, p, msg) + "');\n"); builder.append("});\n"); } } builder.append(";\n"); } protected void generateInternalTransition(InternalTransition t, Message msg, Port p, StringBuilder builder, Context ctx) { if (t.getEvent().size() == 0) { if (t.eContainer() instanceof StateMachine) { builder.append("this.statemachine.to(null)"); } else { builder.append(ThingMLElementHelper.qname(((State) t.eContainer()), "_") + ".to(null)"); } if (t.getGuard() != null) { builder.append(".when((message) => {return "); ctx.getCompiler().getThingActionCompiler().generate(t.getGuard(), builder, ctx); builder.append(";})"); } } else { if (t.eContainer() instanceof StateMachine) { builder.append("this.statemachine.to(null)"); } else { builder.append(ThingMLElementHelper.qname(((State) t.eContainer()), "_") + ".to(null)"); } builder.append(".when((" + msg.getName() + ") => {"); builder.append("return " + msg.getName() + "._port === '" + p.getName() + "' && " + msg.getName() + "._msg === '" + msg.getName() + "'"); if (t.getGuard() != null) { builder.append(" && "); ctx.getCompiler().getThingActionCompiler().generate(t.getGuard(), builder, ctx); } builder.append(";})"); } if (t.getAction() != null) { String debugString = ""; if (debugProfile.isDebugBehavior()) debugString = "" + ThingMLHelpers.findContainingThing(t).getName() + "_print_debug(this, '" + ctx.traceInternal(ThingMLHelpers.findContainingThing(t), p, msg) + "');\n"; generateHandlerAction(t, msg, builder, ctx, debugString); } else { if (p != null) { if (debugProfile.isDebugBehavior()) { builder.append(".effect(() => {\n"); builder.append("" + ThingMLHelpers.findContainingThing(t).getName() + "_print_debug(this, '" + ctx.traceInternal(ThingMLHelpers.findContainingThing(t), p, msg) + "');\n"); builder.append("});\n"); } } } builder.append(";\n"); } protected void generatePrintDebugFunction(Thing thing, StringBuilder builder, Context ctx) { builder.append("//Trace function for " + thing.getName() + "\n"); builder.append("function " + thing.getName() + "_print_debug(instance, msg) {\n"); builder.append("if(instance.debug) {\n"); if (!ctx.getDebugWithID()) { builder.append("console.log(instance.name + msg +'');\n"); } builder.append("}\n"); builder.append("}\n\n"); } }