All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javax0.jamal.debugger.TcpDebugger Maven / Gradle / Ivy

The newest version!
package javax0.jamal.debugger;

import javax0.jamal.api.Debuggable;
import javax0.jamal.api.Debugger;

import java.io.*;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * A simple TCP-based debugger. This was created as a proof of concept and reached a PoC state.
 * It does not support all the features of the debugger, only those that were developedin the PoC phase.
 * It is not supposed to be used in production.
 * 

* The "real" debugger is the http server based debugger that also comes with a react.js based client. */ public abstract class TcpDebugger implements Debugger { private Debugger.Stub stub; public void init(Debugger.Stub stub) throws Exception { this.stub = stub; } public abstract InputStream getIn(); public abstract OutputStream getOut(); public abstract void connect() throws IOException; private int currentLevel; private int stepLevel; private RunState state = RunState.STEP_IN; String inputBefore = ""; CharSequence input; String inputAfter = ""; String output = ""; String macros = ""; boolean binary = true; /** * Read an number from the input stream. *

* The number will be represented in the format {@code nDDDDD} format, where {@code n} is a single decimal number * character and {@code DDDDD} is a decimal number of {@code n} characters. For example {@code 3123} will mean the * number 123. * * @return the number read or -1 if there was an error */ private int numberIn() throws IOException { final var in = getIn(); byte[] len = new byte[1]; if (in.read(len) != 1) { return -1; } int length = len[0] - '0'; if (length < 1 || length > 9) { return -1; } byte[] n = new byte[length]; if (in.read(n) != n.length) { return -1; } int result = 0; for (int i = 0; i < length; i++) { result = 10 * result; int d = n[i] - '0'; if (d < 0 || d > 9) { return -1; } result += d; } return result; } private void sendBuffer(String s) throws IOException { final var out = getOut(); if (binary) { numberOut(s.length()); } out.write(s.getBytes(StandardCharsets.UTF_8)); out.flush(); } private void sendHumanMessage(final String s) throws IOException { if (!binary) { sendBuffer(s); } } private void sendMessage(String message) throws IOException { final var out = getOut(); if (binary) { out.write("13MSG".getBytes(StandardCharsets.UTF_8)); } sendBuffer(message); } private void numberOut(int number) throws IOException { final var out = getOut(); final String num = "" + number; final int len = num.length(); if (len > 9) { throw new IllegalArgumentException("The level is larger than 10^10. This is total WTF, probably internal error."); } if (binary) { out.write(("" + len).getBytes(StandardCharsets.UTF_8)); } out.write(num.getBytes(StandardCharsets.UTF_8)); out.flush(); } private byte[] readLine() throws IOException { final var in = getIn(); byte[] buffer; if (binary) { int len = numberIn(); if (len == -1) { sendMessage("Invalid length for the string to be executed\n"); return null; } buffer = new byte[len]; if (in.read(buffer) != len) { sendMessage("Error reading expression"); return null; } return buffer; } else { final var reader = new BufferedReader(new InputStreamReader(in)); return reader.readLine().getBytes(StandardCharsets.UTF_8); } } private void handle() { if (state != RunState.STEP_IN && (state != RunState.STEP || currentLevel > stepLevel)) { return; } try { final var in = getIn(); final var out = getOut(); boolean prompt = true; while (true) { if (prompt) { sendMessage("\n" + currentLevel + ":" + "> "); } prompt = true; out.flush(); byte[] command = new byte[1]; int commandLength = in.read(command); if (commandLength == 1) { final List scopes; switch (command[0]) { case 'q': // exit the debugger and abort the execution of the processor throw new IllegalArgumentException("Debugger was aborted."); case 'r': // run the processor to the end state = RunState.RUN; return; case 's': // step over the macro, do not step into state = RunState.STEP; stepLevel = currentLevel; return; case 'S': // step into the macro state = RunState.STEP_IN; return; case 'i': // send the input of the required level to the client sendBuffer(inputBefore); break; case 'I': // send the input after of the required level to the client sendBuffer(inputAfter); break; case 'o': // send the output of the required level to the client sendBuffer(output); break; case 'm': // send the macro text of the required level to the client sendBuffer(macros); break; case 'l': // send the current level to the client numberOut(currentLevel); break; case 'x': // execute a macro in the current processor at the current level byte[] buffer = readLine(); if (buffer != null) { final RunState save = state; state = RunState.RUN; stub.process(new String(buffer, StandardCharsets.UTF_8)); state = save; } break; case 'b': // list built in macros scopes = stub.getScopeList(); sendHumanMessage("Levels: "); numberOut(scopes.size()); sendHumanMessage("\n"); int i = 1; for (final var scope : scopes) { sendHumanMessage("Level: "); numberOut(i); sendHumanMessage("\n"); final var macros = scope.getMacros(); if (binary) { numberOut(macros.size()); } int j = 1; for (final var macro : macros.values()) { sendHumanMessage("" + j + ": "); j++; sendBuffer(macro.getId()); sendHumanMessage("\n"); } i++; } break; case 'H': binary = false; break; case 'u': // list user defined macros scopes = stub.getScopeList(); sendHumanMessage("Levels: "); numberOut(scopes.size()); sendHumanMessage("\n"); int j = 1; for (final var scope : scopes) { sendHumanMessage("Level: "); numberOut(j); sendHumanMessage("\n"); final var macros = scope.getUdMacros(); if (binary) { numberOut(macros.size()); } int k = 1; for (final var macro : macros.values()) { sendHumanMessage("" + k + ": "); k++; if (macro instanceof Debuggable.UserDefinedMacro) { final var ud = (Debuggable.UserDefinedMacro) macro; sendBuffer(ud.getOpenStr()); sendHumanMessage("@define "); sendBuffer(macro.getId()); if (binary) { numberOut(ud.getParameters().length); } String sep = ""; sendHumanMessage("("); for (final var parameter : ud.getParameters()) { sendHumanMessage(sep); sep = ","; sendBuffer(parameter); } sendHumanMessage(")="); sendBuffer(ud.getContent()); sendBuffer(ud.getCloseStr()); } else { sendBuffer(macro.getId()); sendBuffer(macro.getClass().getName()); } sendHumanMessage("\n"); } j++; } break; case 'h': // send help text sendMessage("q QUIT | s STEP | S STEP IN | l LEVEL | h HELP |\n" + "i INPUT BEFORE | I INPUT AFTER | o OUTPUT | m START TEXT |\n" + "x EXECUTE | H human"); break; default: prompt = false; if (!Character.isWhitespace(command[0])) { sendMessage("Invalid character '" + ((char) command[0]) + "'\n"); } break; } } } } catch (SocketException se) { try { connect(); } catch (IOException e) { throw new IllegalArgumentException("Debugger cannot reconnect broken socket"); } } catch (Exception e) { throw new IllegalArgumentException("There was an exception in the debugger.", e); } } @Override public void setStart(CharSequence macro) { macros = macro.toString(); } @Override public void setBefore(int level, CharSequence input) { currentLevel = level; this.input = input; this.inputBefore = input.toString(); output = ""; macros = ""; inputAfter = ""; } @Override public void setAfter(int level, CharSequence output) { currentLevel = level; inputAfter = input.toString(); this.output = output.toString(); handle(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy