diff --git a/src/org/antlr/v4/server/ANTLRHttpServer.java b/src/org/antlr/v4/server/ANTLRHttpServer.java index 351668d..5f5e19e 100644 --- a/src/org/antlr/v4/server/ANTLRHttpServer.java +++ b/src/org/antlr/v4/server/ANTLRHttpServer.java @@ -1,149 +1,44 @@ package org.antlr.v4.server; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.stream.JsonReader; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.antlr.v4.server.persistent.PersistenceLayer; -import org.antlr.v4.server.persistent.cloudstorage.CloudStoragePersistenceLayer; -import org.antlr.v4.server.unique.DummyUniqueKeyGenerator; -import org.antlr.v4.server.unique.UniqueKeyGenerator; -import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.antlr.v4.server.persistence.PersistenceLayer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.resource.PathResource; import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.slf4j.LoggerFactory; - import java.io.*; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Optional; - -import static org.antlr.v4.server.GrammarProcessor.interp; +import java.nio.file.Paths; public class ANTLRHttpServer { public static final String IMAGES_DIR = "/tmp/antlr-images"; + public static final int UUID_LEN = 36; - public static class ParseServlet extends DefaultServlet { - static final ch.qos.logback.classic.Logger LOGGER = - (ch.qos.logback.classic.Logger)LoggerFactory.getLogger(ANTLRHttpServer.class); + public static final PersistenceLayer persistenceLayer = PersistenceLayer.newInstance("local"); + public static class RouterServlet extends DefaultServlet { @Override - public void doPost(HttpServletRequest request, HttpServletResponse response) - throws IOException - { - - JsonObject jsonResponse = new JsonObject(); - try { - response.setContentType("text/plain;charset=utf-8"); - response.setContentType("text/html;"); - response.addHeader("Access-Control-Allow-Origin", "*"); - - JsonObject jsonObj = JsonParser.parseReader(request.getReader()).getAsJsonObject(); -// System.out.println(jsonObj); - - String grammar = jsonObj.get("grammar").getAsString(); - String lexGrammar = jsonObj.get("lexgrammar").getAsString(); // can be null - String input = jsonObj.get("input").getAsString(); - String startRule = jsonObj.get("start").getAsString(); - - StringBuilder logMsg = new StringBuilder(); - logMsg.append("GRAMMAR:\n"); - logMsg.append(grammar); - logMsg.append("\nLEX GRAMMAR:\n"); - logMsg.append(lexGrammar); - logMsg.append("\nINPUT:\n\"\"\""); - logMsg.append(input); - logMsg.append("\"\"\"\n"); - logMsg.append("STARTRULE: "); - logMsg.append(startRule); - logMsg.append('\n'); - LOGGER.info(logMsg.toString()); - - if (grammar.strip().length() == 0 && lexGrammar.strip().length() == 0) { - jsonResponse.addProperty("arg_error", "missing either combined grammar or lexer and " + - "parser both"); - } - else if (grammar.strip().length() == 0 && lexGrammar.strip().length() > 0) { - jsonResponse.addProperty("arg_error", "missing parser grammar"); - } - else if (startRule.strip().length() == 0) { - jsonResponse.addProperty("arg_error", "missing start rule"); - } - else if (input.length() == 0) { - jsonResponse.addProperty("arg_error", "missing input"); - } - else { - try { - jsonResponse = interp(grammar, lexGrammar, input, startRule); - } - catch (ParseCancellationException pce) { - jsonResponse.addProperty("exception_trace", "parser timeout ("+GrammarProcessor.MAX_PARSE_TIME_MS+"ms)"); - } - catch (Throwable e) { - e.printStackTrace(System.err); - } - } + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String path = request.getPathInfo(); + path = path.substring(1); // remove '/' on front + if ( path.length()== UUID_LEN ) { + System.out.printf("UUID "+path); + // Redirect to the usual "/" ULR (index.html) with ?uuid=UUID + response.sendRedirect("/?uuid="+path); } - catch (Exception e) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - e.printStackTrace(pw); - jsonResponse.addProperty("exception_trace", sw.toString()); - jsonResponse.addProperty("exception", e.getMessage()); + else { + super.doGet(request, response); } - LOGGER.info("RESULT:\n"+jsonResponse); - response.setStatus(HttpServletResponse.SC_OK); - PrintWriter w = response.getWriter(); - w.write(new Gson().toJson(jsonResponse)); - w.flush(); - } - } - - public static class ShareServlet extends DefaultServlet { - static final ch.qos.logback.classic.Logger LOGGER = - (ch.qos.logback.classic.Logger)LoggerFactory.getLogger(ANTLRHttpServer.class); - - @Override - public void doPost(HttpServletRequest request, HttpServletResponse response) - throws IOException - { - final JsonObject jsonResponse = new JsonObject(); - try { - response.setContentType("text/plain;charset=utf-8"); - response.setContentType("text/html;"); - response.addHeader("Access-Control-Allow-Origin", "*"); - - JsonObject jsonObj = JsonParser.parseReader(request.getReader()).getAsJsonObject(); - PersistenceLayer persistenceLayer = new CloudStoragePersistenceLayer(); - UniqueKeyGenerator keyGen = new DummyUniqueKeyGenerator(); - Optional uniqueKey = keyGen.generateKey(); - persistenceLayer.persist(new Gson().toJson(jsonResponse).getBytes(StandardCharsets.UTF_8), - uniqueKey.orElseThrow()); - - jsonResponse.addProperty("resource_id", uniqueKey.orElseThrow()); - } - catch (Exception e) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - e.printStackTrace(pw); - jsonResponse.addProperty("exception_trace", sw.toString()); - jsonResponse.addProperty("exception", e.getMessage()); - - } - LOGGER.info("RESULT:\n"+jsonResponse); - response.setStatus(HttpServletResponse.SC_OK); - PrintWriter w = response.getWriter(); - w.write(new Gson().toJson(jsonResponse)); - w.flush(); } } @@ -166,15 +61,11 @@ public static void main(String[] args) throws Exception { ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); context.setContextPath("/"); - context.addServlet(new ServletHolder(new ParseServlet()), "/parse/*"); - context.addServlet(new ServletHolder(new ShareServlet()), "/share/*"); - - ServletHolder holderHome = new ServletHolder("static-home", DefaultServlet.class); - holderHome.setInitParameter("resourceBase", "static"); - holderHome.setInitParameter("dirAllowed","true"); - holderHome.setInitParameter("pathInfoOnly","true"); - context.addServlet(holderHome,"/*"); - + context.setBaseResource(new PathResource(Paths.get("static"))); + context.addServlet(RouterServlet.class, "/*"); + context.addServlet(ParseServlet.class, "/parse/*"); + context.addServlet(ShareServlet.class, "/share/*"); + context.addServlet(LoadServlet.class, "/load/*"); server.setHandler(context); server.start(); diff --git a/src/org/antlr/v4/server/GrammarProcessor.java b/src/org/antlr/v4/server/GrammarProcessor.java index f7d76e2..5b1a8bf 100644 --- a/src/org/antlr/v4/server/GrammarProcessor.java +++ b/src/org/antlr/v4/server/GrammarProcessor.java @@ -16,7 +16,7 @@ import static org.antlr.v4.gui.Interpreter.profilerColumnNames; import static org.antlr.v4.server.ANTLRHttpServer.IMAGES_DIR; -import static org.antlr.v4.server.ANTLRHttpServer.ParseServlet.LOGGER; +import static org.antlr.v4.server.ParseServlet.LOGGER; import static us.parr.lib.ParrtSys.execInDir; import java.io.*; diff --git a/src/org/antlr/v4/server/LoadServlet.java b/src/org/antlr/v4/server/LoadServlet.java new file mode 100644 index 0000000..e0adfc9 --- /dev/null +++ b/src/org/antlr/v4/server/LoadServlet.java @@ -0,0 +1,43 @@ +package org.antlr.v4.server; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +public class LoadServlet extends DefaultServlet { + static final ch.qos.logback.classic.Logger LOGGER = + (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ANTLRHttpServer.class); + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException { + String uuid = request.getParameter("uuid"); + final JsonObject jsonResponse = new JsonObject(); + try { + response.setContentType("text/plain;charset=utf-8"); + response.setContentType("text/html;"); + response.addHeader("Access-Control-Allow-Origin", "*"); + System.err.println("uuid: " + uuid); + byte[] jsonBytes = ANTLRHttpServer.persistenceLayer.retrieve(uuid); + LOGGER.info("LOAD:\n" + jsonResponse); + response.setStatus(HttpServletResponse.SC_OK); + PrintWriter w = response.getWriter(); + w.write(new String(jsonBytes)); + w.flush(); + } + catch (Exception e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + jsonResponse.addProperty("exception_trace", sw.toString()); + jsonResponse.addProperty("exception", e.getMessage()); + } + } +} diff --git a/src/org/antlr/v4/server/ParseServlet.java b/src/org/antlr/v4/server/ParseServlet.java new file mode 100644 index 0000000..19736d3 --- /dev/null +++ b/src/org/antlr/v4/server/ParseServlet.java @@ -0,0 +1,84 @@ +package org.antlr.v4.server; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +import static org.antlr.v4.server.GrammarProcessor.interp; + +public class ParseServlet extends DefaultServlet { + static final ch.qos.logback.classic.Logger LOGGER = + (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ANTLRHttpServer.class); + + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws IOException { + + JsonObject jsonResponse = new JsonObject(); + try { + response.setContentType("text/plain;charset=utf-8"); + response.setContentType("text/html;"); + response.addHeader("Access-Control-Allow-Origin", "*"); + + JsonObject jsonObj = JsonParser.parseReader(request.getReader()).getAsJsonObject(); +// System.out.println(jsonObj); + + String grammar = jsonObj.get("grammar").getAsString(); + String lexGrammar = jsonObj.get("lexgrammar").getAsString(); // can be null + String input = jsonObj.get("input").getAsString(); + String startRule = jsonObj.get("start").getAsString(); + + StringBuilder logMsg = new StringBuilder(); + logMsg.append("GRAMMAR:\n"); + logMsg.append(grammar); + logMsg.append("\nLEX GRAMMAR:\n"); + logMsg.append(lexGrammar); + logMsg.append("\nINPUT:\n\"\"\""); + logMsg.append(input); + logMsg.append("\"\"\"\n"); + logMsg.append("STARTRULE: "); + logMsg.append(startRule); + logMsg.append('\n'); + LOGGER.info(logMsg.toString()); + + if (grammar.strip().length() == 0 && lexGrammar.strip().length() == 0) { + jsonResponse.addProperty("arg_error", "missing either combined grammar or lexer and " + + "parser both"); + } else if (grammar.strip().length() == 0 && lexGrammar.strip().length() > 0) { + jsonResponse.addProperty("arg_error", "missing parser grammar"); + } else if (startRule.strip().length() == 0) { + jsonResponse.addProperty("arg_error", "missing start rule"); + } else if (input.length() == 0) { + jsonResponse.addProperty("arg_error", "missing input"); + } else { + try { + jsonResponse = interp(grammar, lexGrammar, input, startRule); + } catch (ParseCancellationException pce) { + jsonResponse.addProperty("exception_trace", "parser timeout (" + GrammarProcessor.MAX_PARSE_TIME_MS + "ms)"); + } catch (Throwable e) { + e.printStackTrace(System.err); + } + } + } catch (Exception e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + jsonResponse.addProperty("exception_trace", sw.toString()); + jsonResponse.addProperty("exception", e.getMessage()); + } + LOGGER.info("RESULT:\n" + jsonResponse); + response.setStatus(HttpServletResponse.SC_OK); + PrintWriter w = response.getWriter(); + w.write(new Gson().toJson(jsonResponse)); + w.flush(); + } +} diff --git a/src/org/antlr/v4/server/ShareServlet.java b/src/org/antlr/v4/server/ShareServlet.java new file mode 100644 index 0000000..e9dc645 --- /dev/null +++ b/src/org/antlr/v4/server/ShareServlet.java @@ -0,0 +1,58 @@ +package org.antlr.v4.server; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.antlr.v4.server.persistence.PersistenceLayer; +import org.antlr.v4.server.persistence.CloudStoragePersistenceLayer; +import org.antlr.v4.server.unique.DummyUniqueKeyGenerator; +import org.antlr.v4.server.unique.UniqueKeyGenerator; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +import static org.antlr.v4.server.ANTLRHttpServer.persistenceLayer; + +public class ShareServlet extends DefaultServlet { + static final ch.qos.logback.classic.Logger LOGGER = + (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ANTLRHttpServer.class); + + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws IOException { + final JsonObject jsonResponse = new JsonObject(); + try { + response.setContentType("text/plain;charset=utf-8"); + response.setContentType("text/html;"); + response.addHeader("Access-Control-Allow-Origin", "*"); + + JsonObject jsonObj = JsonParser.parseReader(request.getReader()).getAsJsonObject(); + UniqueKeyGenerator keyGen = new DummyUniqueKeyGenerator(); + Optional uniqueKey = keyGen.generateKey(); + persistenceLayer.persist(new Gson().toJson(jsonObj).getBytes(StandardCharsets.UTF_8), + uniqueKey.orElseThrow()); + + jsonResponse.addProperty("uuid", uniqueKey.orElseThrow()); + } + catch (Exception e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + jsonResponse.addProperty("exception_trace", sw.toString()); + jsonResponse.addProperty("exception", e.getMessage()); + + } + LOGGER.info("RESULT:\n" + jsonResponse); + response.setStatus(HttpServletResponse.SC_OK); + PrintWriter w = response.getWriter(); + w.write(new Gson().toJson(jsonResponse)); + w.flush(); + } +} diff --git a/src/org/antlr/v4/server/persistent/cloudstorage/CloudStoragePersistenceLayer.java b/src/org/antlr/v4/server/persistence/CloudStoragePersistenceLayer.java similarity index 93% rename from src/org/antlr/v4/server/persistent/cloudstorage/CloudStoragePersistenceLayer.java rename to src/org/antlr/v4/server/persistence/CloudStoragePersistenceLayer.java index 7822599..517066c 100644 --- a/src/org/antlr/v4/server/persistent/cloudstorage/CloudStoragePersistenceLayer.java +++ b/src/org/antlr/v4/server/persistence/CloudStoragePersistenceLayer.java @@ -1,11 +1,11 @@ -package org.antlr.v4.server.persistent.cloudstorage; +package org.antlr.v4.server.persistence; import com.google.cloud.storage.BlobId; import com.google.cloud.storage.BlobInfo; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; import org.antlr.v4.server.ANTLRHttpServer; -import org.antlr.v4.server.persistent.PersistenceLayer; +import org.antlr.v4.server.persistence.PersistenceLayer; import org.slf4j.LoggerFactory; import java.io.IOException; diff --git a/src/org/antlr/v4/server/persistence/LocalStorage.java b/src/org/antlr/v4/server/persistence/LocalStorage.java new file mode 100644 index 0000000..6a88334 --- /dev/null +++ b/src/org/antlr/v4/server/persistence/LocalStorage.java @@ -0,0 +1,20 @@ +package org.antlr.v4.server.persistence; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.InvalidKeyException; + +public class LocalStorage implements PersistenceLayer { + @Override + public void persist(byte[] byteBuffer, String identifier) throws IOException { + String objectName = identifier + ".json"; + Files.write(Paths.get("/tmp/" + objectName), byteBuffer); + } + + @Override + public byte[] retrieve(String identifier) throws IOException, InvalidKeyException { + String objectName = identifier + ".json"; + return Files.readAllBytes(Paths.get("/tmp/" + objectName)); + } +} diff --git a/src/org/antlr/v4/server/persistent/PersistenceLayer.java b/src/org/antlr/v4/server/persistence/PersistenceLayer.java similarity index 65% rename from src/org/antlr/v4/server/persistent/PersistenceLayer.java rename to src/org/antlr/v4/server/persistence/PersistenceLayer.java index 74f8673..0858123 100644 --- a/src/org/antlr/v4/server/persistent/PersistenceLayer.java +++ b/src/org/antlr/v4/server/persistence/PersistenceLayer.java @@ -1,9 +1,19 @@ -package org.antlr.v4.server.persistent; +package org.antlr.v4.server.persistence; import java.io.IOException; import java.security.InvalidKeyException; public interface PersistenceLayer { + static final LocalStorage local = new LocalStorage(); + static final CloudStoragePersistenceLayer cloud = new CloudStoragePersistenceLayer(); + + public static PersistenceLayer newInstance(String where) { + switch (where) { + case "cloud" : return cloud; + default : return local; + } + } + /** * Persists a byte buffer in the underlying storage system. * @param buffer byte buffer diff --git a/static/index.html b/static/index.html index 025356e..b4097e6 100644 --- a/static/index.html +++ b/static/index.html @@ -104,8 +104,9 @@

Start rule 
- program - + + +
diff --git a/static/js/antlr-client.js b/static/js/antlr-client.js index d942ede..fc93481 100644 --- a/static/js/antlr-client.js +++ b/static/js/antlr-client.js @@ -1,8 +1,10 @@ "use strict"; -let ANTLR_SERVICE = "/parse/"; +const ANTLR_SERVICE = "/parse/"; +const SAVE = "/share/"; +const LOAD = "/load/"; -let SAMPLE_PARSER = +const SAMPLE_PARSER = "parser grammar ExprParser;\n" + "options { tokenVocab=ExprLexer; }\n" + "\n" + @@ -28,7 +30,7 @@ let SAMPLE_PARSER = "\n" + "func : ID '(' expr (',' expr)* ')' ;" -let SAMPLE_LEXER = +const SAMPLE_LEXER = "// DELETE THIS CONTENT IF YOU PUT COMBINED GRAMMAR IN Parser TAB\n" + "lexer grammar ExprLexer;\n" + "\n" + @@ -47,12 +49,14 @@ let SAMPLE_LEXER = "ID: [a-zA-Z_][a-zA-Z_0-9]* ;\n" + "WS: [ \\t\\n\\r\\f]+ -> skip ;"; -let SAMPLE_INPUT = +const SAMPLE_INPUT = "f(x,y) {\n" + " a = 3+foo;\n" + " x and y;\n" + "}"; +const SAMPLE_START_RULE = "program" + function processANTLRResults(response) { let parserSession = $("#grammar").data("parserSession") @@ -246,6 +250,29 @@ async function run_antlr() { }); } +async function share_grammar() { + let parserSession = $("#grammar").data("parserSession") + let lexerSession = $("#grammar").data("lexerSession") + let g = parserSession.getValue() + let lg = lexerSession.getValue(); + let I = $("#input").data("session").getValue(); + let s = $('#start').text(); + + $("#profile_choice").show(); + + await axios.post(SAVE, + {grammar: g, lexgrammar: lg, input: I, start: s} + ) + .then((response) => { + console.log("Shared as "+response.data.uuid) + }) + .catch((error) => { + if( error.response ){ + console.log(error.response.data); // => the response payload + } + }); +} + function initParseTreeView() { $("#svgtreetab").show(); $("#treetab").show(); @@ -461,8 +488,8 @@ function createAceANTLRMode() { } function createGrammarEditor() { - var parserSession = ace.createEditSession(SAMPLE_PARSER); - var lexerSession = ace.createEditSession(SAMPLE_LEXER); + var parserSession = ace.createEditSession(loadInitialParser()); + var lexerSession = ace.createEditSession(loadInitialLexer()); var editor = ace.edit("grammar"); $("#grammar").data("parserSession", parserSession); @@ -508,7 +535,7 @@ function removeAllMarkers(session) { function createInputEditor() { var input = ace.edit("input"); - let session = ace.createEditSession(SAMPLE_INPUT); + let session = ace.createEditSession(loadInitialInput()); $("#input").data("session", session); $("#input").data("editor", input); input.setSession(session); @@ -625,8 +652,66 @@ function setUpDragAndDrop() { } } +function loadInitialParser() { + const state = $("body").data("state") + if (state && state.grammar) { + return state.grammar; + } + return SAMPLE_PARSER; +} + +function loadInitialLexer() { + const state = $("body").data("state") + if (state && state.lexgrammar) { + return state.lexgrammar; + } + return SAMPLE_LEXER; +} + +function loadInitialInput() { + const state = $("body").data("state") + if (state && state.input) { + return state.input; + } + return SAMPLE_INPUT; +} + +function loadInitialStartRule() { + const state = $("body").data("state") + if (state && state.start) { + return state.start; + } + return SAMPLE_START_RULE +} + +async function loadState(uuid) { + $("body").data("uuid", uuid); + console.log("UUID: " + uuid); + await axios.get(LOAD+"?uuid="+uuid) + .then((response) => { + console.log("Loaded"+response.data) + $("body").data("state", response.data) + return response.data; + }) + .catch((error) => { + if( error.response ){ + console.log(error.response.data); // => the response payload + } + }); +} + // MAIN -$(document).ready(function() { +$(document).ready(async function() { + // Remove any ?uuid=UUID arguments from the URL in browser window (messes up backup button) + const args = new URLSearchParams(window.location.search); + const uuid = args.get("uuid"); + if (uuid) { + await loadState(uuid); + if (false) { + history.replaceState(null, null, "/"); + } + } + String.prototype.sliceReplace = function (start, end, repl) { return this.substring(0, start) + repl + this.substring(end); }; @@ -636,6 +721,7 @@ $(document).ready(function() { var editor = createGrammarEditor(); setupGrammarTabs(editor); createInputEditor(); + $("#start").html(loadInitialStartRule()); setupTreeTabs();