package com.siberika.idea.pascal.debugger.gdb.parser; import com.intellij.util.SmartList; import org.jetbrains.annotations.NotNull; import java.util.Collection; /** * Author: George Bakhtadze * Date: 29/03/2017 */ public class GdbMiParser { private final String input; private final int end; private int pos = 0; private char lastChar; public GdbMiParser(@NotNull String input) { this.input = input; this.end = input.length(); } private GdbMiLine parseLine() { GdbMiLine result = new GdbMiLine(parseToken(), getType(lastChar), parseClass()); parseValues(result.getResults()); return result; } // ( "," result )* private void parseValues(GdbMiResults dest) { while (',' == lastChar) { parseResult(dest); nextChar(); } } // variable "=" value private void parseResult(GdbMiResults dest) { String name = parseString(); if (null == name) { return; } if ('=' == lastChar) { Object value = parseValueOrStop(); dest.setValue(name, value); } else { throw new GdbMiParserException("Error parsing result", pos, input); } } private Object parseValueOrStop() { Object value = parseValue(); if (value != null) { return value; } else { throw new GdbMiParserException("Error parsing value", pos, input); } } // const | tuple | list private Object parseValue() { switch (nextChar()) { case '"': return parseConst(); case '{': return parseTuple(); case '[': return parseList(); default: return null; } } // "[]" | "[" value ( "," value )* "]" | "[" result ( "," result )* "]" private Object parseList() { Collection<Object> res = new SmartList<Object>(); int savePos = pos; try { parseValuesList(res); } catch (GdbMiParserException e) { pos = savePos; parseResultsList(res); } if (lastChar != ']') { throw new GdbMiParserException("Error parsing list", pos, input); } return res; } // result ( "," result )* private void parseResultsList(Collection<Object> res) { parseResultToList(res); nextChar(); while (',' == lastChar) { parseResultToList(res); nextChar(); } } private void parseResultToList(Collection<Object> res) { GdbMiResults r = new GdbMiResults(); parseResult(r); res.add(r); } // value ( "," value )* private void parseValuesList(Collection<Object> res) { Object value = parseValue(); if (null == value) { // empty list if (']' == lastChar) { return; } else { throw new GdbMiParserException("Error parsing values list", pos, input); } } res.add(value); nextChar(); while (',' == lastChar) { res.add(parseValueOrStop()); nextChar(); } } // "{}" | "{" result ( "," result )* "}" private Object parseTuple() { GdbMiResults res = new GdbMiResults(); parseResult(res); // Empty tuple if ('}' == lastChar) { return res; } nextChar(); parseValues(res); if ('}' != lastChar) { throw new GdbMiParserException("Error parsing tuple", pos, input); } return res; } // c-string private Object parseConst() { StringBuilder t = new StringBuilder(); while (nextChar() != 0 && lastChar != '"') { t = t.append(lastChar); if ('\\' == lastChar) { t = t.append(nextChar()); } } if ('"' != lastChar) { throw new GdbMiParserException("Error parsing const", pos, input); } return t.toString(); } private String parseClass() { return parseString(); } private String parseString() { StringBuilder t = new StringBuilder(); while ((nextChar() >= 'a') && (lastChar <= 'z') || ('-' == lastChar) || ('_' == lastChar)) { t = t.append(lastChar); } return t.length() > 0 ? t.toString() : null; } private GdbMiLine.Type getType(char typeChar) { switch (typeChar) { case '*': return GdbMiLine.Type.EXEC_ASYNC; case '+': return GdbMiLine.Type.STATUS_ASYNC; case '=': return GdbMiLine.Type.NOTIFY_ASYNC; case '^': return GdbMiLine.Type.RESULT_RECORD; case '~': return GdbMiLine.Type.CONSOLE_STREAM; case '@': return GdbMiLine.Type.TARGET_STREAM; case '&': return GdbMiLine.Type.LOG_STREAM; } return null; } private Long parseToken() { StringBuilder t = new StringBuilder(); while (Character.isDigit(nextChar())) { t = t.append(lastChar); } return t.length() > 0 ? Long.parseLong(t.toString()) : null; } private char nextChar() { lastChar = pos < end ? input.charAt(pos++) : 0; return lastChar; } public static GdbMiLine parseLine(String line) { return new GdbMiParser(line).parseLine(); } /* output → ( out-of-band-record )* [ result-record ] "(gdb)" nl result-record → [ token ] "^" result-class ( "," result )* nl out-of-band-record → async-record | stream-record async-record → exec-async-output | status-async-output | notify-async-output exec-async-output → [ token ] "*" async-output nl status-async-output → [ token ] "+" async-output nl notify-async-output → [ token ] "=" async-output nl async-output → async-class ( "," result )* result-class → "done" | "running" | "connected" | "error" | "exit" async-class → "stopped" | others (where others will be added depending on the needs—this is still in development). result → variable "=" value variable → string value → const | tuple | list const → c-string tuple → "{}" | "{" result ( "," result )* "}" list → "[]" | "[" value ( "," value )* "]" | "[" result ( "," result )* "]" stream-record → console-stream-output | target-stream-output | log-stream-output console-stream-output → "~" c-string nl target-stream-output → "@" c-string nl log-stream-output → "&" c-string nl nl → CR | CR-LF token → any sequence of digits. */ }