work save
This commit is contained in:
parent
d3abf822fb
commit
9fd06a9837
|
@ -12,6 +12,9 @@
|
|||
#include <QPlainTextEdit>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
Debugger::Debugger(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
@ -63,6 +66,7 @@ bool Debugger::start()
|
|||
connect(this, &Debugger::localsReady,pMainWindow,&MainWindow::onLocalsReady);
|
||||
connect(mReader, &DebugReader::cmdStarted,pMainWindow, &MainWindow::disableDebugActions);
|
||||
connect(mReader, &DebugReader::cmdFinished,pMainWindow, &MainWindow::enableDebugActions);
|
||||
|
||||
connect(mReader, &DebugReader::breakpointInfoGetted, mBreakpointModel,
|
||||
&BreakpointModel::updateBreakpointNumber);
|
||||
|
||||
|
@ -455,23 +459,17 @@ void Debugger::syncFinishedParsing()
|
|||
}
|
||||
|
||||
// An evaluation variable has been processed. Forward the results
|
||||
if (mReader->doevalready) {
|
||||
if (mReader->evalReady()) {
|
||||
//pMainWindow->updateDebugEval(mReader->mEvalValue);
|
||||
emit evalValueReady(mReader->mEvalValue);
|
||||
mReader->mEvalValue="";
|
||||
mReader->doevalready = false;
|
||||
emit evalValueReady(mReader->evalValue());
|
||||
}
|
||||
|
||||
if (mReader->doupdatememoryview) {
|
||||
emit memoryExamineReady(mReader->mMemoryValue);
|
||||
mReader->mMemoryValue.clear();
|
||||
mReader->doupdatememoryview=false;
|
||||
if (mReader->updateMemory()) {
|
||||
emit memoryExamineReady(mReader->memoryValue());
|
||||
}
|
||||
|
||||
if (mReader->doupdatelocal) {
|
||||
emit localsReady(mReader->mLocalsValue);
|
||||
mReader->mLocalsValue.clear();
|
||||
mReader->doupdatelocal=false;
|
||||
if (mReader->updateLocals()) {
|
||||
emit localsReady(mReader->localsValue());
|
||||
}
|
||||
|
||||
// show command output
|
||||
|
@ -521,16 +519,17 @@ void Debugger::syncFinishedParsing()
|
|||
}
|
||||
}
|
||||
|
||||
if (mReader->doupdateexecution) {
|
||||
if (mReader->mCurrentCmd && mReader->mCurrentCmd->source == DebugCommandSource::Console) {
|
||||
pMainWindow->setActiveBreakpoint(mReader->mBreakPointFile, mReader->mBreakPointLine,false);
|
||||
if (mReader->updateExecution()) {
|
||||
if (mReader->currentCmd() && mReader->currentCmd()->source == DebugCommandSource::Console) {
|
||||
pMainWindow->setActiveBreakpoint(mReader->breakPointFile(), mReader->breakPointLine(),false);
|
||||
} else {
|
||||
pMainWindow->setActiveBreakpoint(mReader->mBreakPointFile, mReader->mBreakPointLine);
|
||||
pMainWindow->setActiveBreakpoint(mReader->breakPointFile(), mReader->breakPointLine());
|
||||
}
|
||||
refreshWatchVars(); // update variable information
|
||||
}
|
||||
|
||||
if (mReader->doreceivedsignal) {
|
||||
if (mReader->signalReceived()) {
|
||||
|
||||
//SignalDialog := CreateMessageDialog(fSignal, mtError, [mbOk]);
|
||||
//SignalCheck := TCheckBox.Create(SignalDialog);
|
||||
|
||||
|
@ -563,7 +562,7 @@ void Debugger::syncFinishedParsing()
|
|||
|
||||
|
||||
// CPU form updates itself when spawned, don't update twice!
|
||||
if ((mReader->doupdatecpuwindow && !spawnedcpuform) && (pMainWindow->cpuDialog()!=nullptr)) {
|
||||
if ((mReader->updateCPUInfo() && !spawnedcpuform) && (pMainWindow->cpuDialog()!=nullptr)) {
|
||||
pMainWindow->cpuDialog()->updateInfo();
|
||||
}
|
||||
}
|
||||
|
@ -903,86 +902,6 @@ void DebugReader::handleExit()
|
|||
doprocessexited=true;
|
||||
}
|
||||
|
||||
void DebugReader::handleFrames()
|
||||
{
|
||||
QString s = getNextLine();
|
||||
|
||||
// Is this a backtrace dump?
|
||||
if (s.startsWith("#")) {
|
||||
if (s.startsWith("#0")) {
|
||||
mDebugger->backtraceModel()->clear();
|
||||
}
|
||||
// Find function name
|
||||
if (!findAnnotation(AnnotationType::TFrameFunctionName))
|
||||
return;
|
||||
|
||||
PTrace trace = std::make_shared<Trace>();
|
||||
trace->funcname = getNextLine();
|
||||
|
||||
// Find argument list start
|
||||
if (!findAnnotation(AnnotationType::TFrameArgs))
|
||||
return;
|
||||
|
||||
// Arguments are either () or detailed list
|
||||
s = getNextLine();
|
||||
|
||||
while (peekNextAnnotation() == AnnotationType::TArgBegin) {
|
||||
|
||||
// argument name
|
||||
if (!findAnnotation(AnnotationType::TArgBegin))
|
||||
return;
|
||||
|
||||
s = s + getNextLine();
|
||||
|
||||
// =
|
||||
if (!findAnnotation(AnnotationType::TArgNameEnd))
|
||||
return;
|
||||
s = s + ' ' + getNextLine() + ' '; // should be =
|
||||
|
||||
// argument value
|
||||
if (!findAnnotation(AnnotationType::TArgValue))
|
||||
return;
|
||||
|
||||
s = s + getNextLine();
|
||||
|
||||
// argument end
|
||||
if (!findAnnotation(AnnotationType::TArgEnd))
|
||||
return;
|
||||
|
||||
s = s + getNextLine();
|
||||
}
|
||||
|
||||
trace->funcname = trace->funcname + s.trimmed();
|
||||
|
||||
// source info
|
||||
if (peekNextAnnotation() == AnnotationType::TFrameSourceBegin) {
|
||||
// Find filename
|
||||
if (!findAnnotation(AnnotationType::TFrameSourceFile))
|
||||
return;
|
||||
trace->filename = getNextLine();
|
||||
// find line
|
||||
if (!findAnnotation(AnnotationType::TFrameSourceLine))
|
||||
return;
|
||||
trace->line = getNextLine().trimmed().toInt();
|
||||
} else {
|
||||
trace->filename = "";
|
||||
trace->line = 0;
|
||||
}
|
||||
mDebugger->backtraceModel()->addTrace(trace);
|
||||
|
||||
// Skip over the remaining frame part...
|
||||
if (!findAnnotation(AnnotationType::TFrameEnd))
|
||||
return;
|
||||
|
||||
// Not another one coming? Done!
|
||||
if (peekNextAnnotation() != AnnotationType::TFrameBegin) {
|
||||
// End of stack trace dump!
|
||||
dobacktraceready = true;
|
||||
}
|
||||
} else
|
||||
doupdatecpuwindow = true;
|
||||
}
|
||||
|
||||
void DebugReader::handleLocalOutput()
|
||||
{
|
||||
// name(spaces)hexvalue(tab)decimalvalue
|
||||
|
@ -1167,9 +1086,22 @@ void DebugReader::processResult(const QByteArray &result)
|
|||
if (!parseOk)
|
||||
return;
|
||||
switch(resultType) {
|
||||
case GDBMIResultType::BreakpointTable:
|
||||
case GDBMIResultType::Frame:
|
||||
case GDBMIResultType::Locals:
|
||||
break;
|
||||
case GDBMIResultType::Breakpoint:
|
||||
handleBreakpoint(parseValue.object());
|
||||
return;
|
||||
case GDBMIResultType::FrameStack:
|
||||
handleStack(parseValue.array());
|
||||
return;
|
||||
case GDBMIResultType::LocalVariables:
|
||||
handleLocalVariables(parseValue.array());
|
||||
return;
|
||||
case GDBMIResultType::Evaluation:
|
||||
handleEvaluation(parseValue.value());
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray name = result.mid(0,pos);
|
||||
|
@ -1227,13 +1159,12 @@ void DebugReader::processExecAsyncRecord(const QByteArray &line)
|
|||
mProcessExited = true;
|
||||
return;
|
||||
}
|
||||
mUpdateExecution = true;
|
||||
mUpdateCPUInfo = true;
|
||||
mBreakPointFile = multiValues["fullname"].pathValue();
|
||||
mBreakPointLine = multiValues["line"].intValue();
|
||||
if (reason == "signal-received") {
|
||||
//todo: signal received
|
||||
return;
|
||||
}
|
||||
if (reason == "breakpoint-hit") {
|
||||
//todo: signal received
|
||||
return;
|
||||
mSignalReceived = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1282,10 +1213,16 @@ void DebugReader::processDebugOutput(const QByteArray& debugOutput)
|
|||
emit parseStarted();
|
||||
|
||||
mConsoleOutput.clear();
|
||||
mLocalsValue.clear();
|
||||
mEvalValue.clear();
|
||||
mMemoryValue.clear();
|
||||
|
||||
//try
|
||||
mUpdateExecution = false;
|
||||
mSignalReceived = false;
|
||||
mUpdateCPUInfo = false;
|
||||
mUpdateLocals = false;
|
||||
mEvalReady = false;
|
||||
|
||||
dobacktraceready = false;
|
||||
dodisassemblerready = false;
|
||||
doregistersready = false;
|
||||
doevalready = false;
|
||||
|
@ -1725,12 +1662,58 @@ QStringList DebugReader::tokenize(const QString &s)
|
|||
void DebugReader::handleBreakpoint(const GDBMIResultParser::ParseObject& breakpoint)
|
||||
{
|
||||
// gdb use system encoding for file path
|
||||
QString filename = QFileInfo(QString::fromLocal8Bit(breakpoint["filename"].value())).absoluteFilePath();
|
||||
QString filename = breakpoint["fullname"].value();
|
||||
int line = breakpoint["line"].intValue();
|
||||
int number = breakpoint["number"].intValue();
|
||||
emit breakpointInfoGetted(filename, line , number);
|
||||
}
|
||||
|
||||
void DebugReader::handleStack(const QList<GDBMIResultParser::ParseValue> & stack)
|
||||
{
|
||||
mDebugger->backtraceModel()->clear();
|
||||
foreach (const GDBMIResultParser::ParseValue& frameValue, stack) {
|
||||
GDBMIResultParser::ParseObject frameObject = frameValue.object();
|
||||
PTrace trace = std::make_shared<Trace>();
|
||||
trace->funcname = frameObject["func"].value();
|
||||
trace->filename = frameObject["fullname"].pathValue();
|
||||
trace->line = frameObject["fullname"].intValue();
|
||||
trace->level = frameObject["level"].intValue(0);
|
||||
trace->address = frameObject["addr"].value();
|
||||
mDebugger->backtraceModel()->addTrace(trace);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugReader::handleLocalVariables(const QList<GDBMIResultParser::ParseValue> &variables)
|
||||
{
|
||||
mUpdateLocals=true;
|
||||
foreach (const GDBMIResultParser::ParseValue& varValue, variables) {
|
||||
GDBMIResultParser::ParseObject varObject = varValue.object();
|
||||
mLocalsValue.append(QString("%1 = %2")
|
||||
.arg(varObject["name"].value(),varObject["value"].value()));
|
||||
}
|
||||
}
|
||||
|
||||
void DebugReader::handleEvaluation(const QString &value)
|
||||
{
|
||||
mEvalReady = true;
|
||||
mEvalValue = value;
|
||||
}
|
||||
|
||||
void DebugReader::handleMemory(const QList<GDBMIResultParser::ParseValue> &rows)
|
||||
{
|
||||
mUpdateMemory = true;
|
||||
foreach (const GDBMIResultParser::ParseValue& row, rows) {
|
||||
GDBMIResultParser::ParseObject rowObject = row.object();
|
||||
QList<GDBMIResultParser::ParseValue> data = rowObject["data"].array();
|
||||
QStringList values;
|
||||
foreach (const GDBMIResultParser::ParseValue& val, data) {
|
||||
values.append(val.value());
|
||||
}
|
||||
mLocalsValue.append(QString("%1 %2")
|
||||
.arg(rowObject["addr"].value(),values.join(" ")));
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray DebugReader::removeToken(const QByteArray &line)
|
||||
{
|
||||
int p=0;
|
||||
|
@ -1746,6 +1729,71 @@ QByteArray DebugReader::removeToken(const QByteArray &line)
|
|||
return line;
|
||||
}
|
||||
|
||||
const QStringList &DebugReader::memoryValue() const
|
||||
{
|
||||
return mMemoryValue;
|
||||
}
|
||||
|
||||
bool DebugReader::updateMemory() const
|
||||
{
|
||||
return mUpdateMemory;
|
||||
}
|
||||
|
||||
const QString &DebugReader::evalValue() const
|
||||
{
|
||||
return mEvalValue;
|
||||
}
|
||||
|
||||
bool DebugReader::evalReady() const
|
||||
{
|
||||
return mEvalReady;
|
||||
}
|
||||
|
||||
const QStringList &DebugReader::localsValue() const
|
||||
{
|
||||
return mLocalsValue;
|
||||
}
|
||||
|
||||
bool DebugReader::updateLocals() const
|
||||
{
|
||||
return mUpdateLocals;
|
||||
}
|
||||
|
||||
bool DebugReader::updateCPUInfo() const
|
||||
{
|
||||
return mUpdateCPUInfo;
|
||||
}
|
||||
|
||||
const PDebugCommand &DebugReader::currentCmd() const
|
||||
{
|
||||
return mCurrentCmd;
|
||||
}
|
||||
|
||||
const QString &DebugReader::breakPointFile() const
|
||||
{
|
||||
return mBreakPointFile;
|
||||
}
|
||||
|
||||
int DebugReader::breakPointLine() const
|
||||
{
|
||||
return mBreakPointLine;
|
||||
}
|
||||
|
||||
const QStringList &DebugReader::consoleOutput() const
|
||||
{
|
||||
return mConsoleOutput;
|
||||
}
|
||||
|
||||
bool DebugReader::signalReceived() const
|
||||
{
|
||||
return mSignalReceived;
|
||||
}
|
||||
|
||||
bool DebugReader::updateExecution() const
|
||||
{
|
||||
return mUpdateExecution;
|
||||
}
|
||||
|
||||
bool DebugReader::processExited() const
|
||||
{
|
||||
return mProcessExited;
|
||||
|
@ -1791,7 +1839,6 @@ bool DebugReader::waitStart()
|
|||
mStartSemaphore.acquire(1);
|
||||
}
|
||||
|
||||
|
||||
void DebugReader::run()
|
||||
{
|
||||
mStop = false;
|
||||
|
@ -2056,6 +2103,7 @@ void BreakpointModel::updateBreakpointNumber(const QString &filename, int line,
|
|||
foreach (PBreakpoint bp, mList) {
|
||||
if (bp->filename == filename && bp->line == line) {
|
||||
bp->number = number;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,9 @@ using PBreakpoint = std::shared_ptr<Breakpoint>;
|
|||
struct Trace {
|
||||
QString funcname;
|
||||
QString filename;
|
||||
QString address;
|
||||
int line;
|
||||
int level;
|
||||
};
|
||||
|
||||
using PTrace = std::shared_ptr<Trace>;
|
||||
|
@ -281,6 +283,31 @@ public:
|
|||
|
||||
bool processExited() const;
|
||||
|
||||
bool updateExecution() const;
|
||||
|
||||
bool signalReceived() const;
|
||||
|
||||
const QStringList &consoleOutput() const;
|
||||
|
||||
int breakPointLine() const;
|
||||
|
||||
const QString &breakPointFile() const;
|
||||
|
||||
const PDebugCommand ¤tCmd() const;
|
||||
|
||||
bool updateCPUInfo() const;
|
||||
|
||||
bool updateLocals() const;
|
||||
|
||||
const QStringList &localsValue() const;
|
||||
|
||||
bool evalReady() const;
|
||||
|
||||
const QString &evalValue() const;
|
||||
|
||||
bool updateMemory() const;
|
||||
|
||||
const QStringList &memoryValue() const;
|
||||
|
||||
signals:
|
||||
void parseStarted();
|
||||
|
@ -302,7 +329,6 @@ private:
|
|||
void handleDisplay();
|
||||
void handleError();
|
||||
void handleExit();
|
||||
void handleFrames();
|
||||
void handleLocalOutput();
|
||||
void handleLocals();
|
||||
void handleMemory();
|
||||
|
@ -319,6 +345,10 @@ private:
|
|||
QStringList tokenize(const QString& s);
|
||||
|
||||
void handleBreakpoint(const GDBMIResultParser::ParseObject& breakpoint);
|
||||
void handleStack(const QList<GDBMIResultParser::ParseValue> & stack);
|
||||
void handleLocalVariables(const QList<GDBMIResultParser::ParseValue> & variables);
|
||||
void handleEvaluation(const QString& value);
|
||||
void handleMemory(const QList<GDBMIResultParser::ParseValue> & rows);
|
||||
void processConsoleOutput(const QByteArray& line);
|
||||
void processResult(const QByteArray& result);
|
||||
void processExecAsyncRecord(const QByteArray& line);
|
||||
|
@ -345,16 +375,11 @@ private:
|
|||
|
||||
//fWatchView: TTreeView;
|
||||
int mIndex;
|
||||
int mBreakPointLine;
|
||||
QString mBreakPointFile;
|
||||
QString mEvalValue;
|
||||
QStringList mMemoryValue;
|
||||
QStringList mLocalsValue;
|
||||
|
||||
QString mSignal;
|
||||
bool mUseUTF8;
|
||||
|
||||
// attempt to cut down on Synchronize calls
|
||||
bool dobacktraceready;
|
||||
bool dodisassemblerready;
|
||||
bool doregistersready;
|
||||
bool doevalready;
|
||||
|
@ -363,12 +388,24 @@ private:
|
|||
bool doreceivedsignal;
|
||||
bool doreceivedsfwarning;
|
||||
bool doupdatememoryview;
|
||||
bool doupdatelocal;
|
||||
|
||||
//
|
||||
bool mInferiorPaused;
|
||||
bool mProcessExited;
|
||||
|
||||
bool mUpdateExecution;
|
||||
bool mSignalReceived;
|
||||
bool mUpdateCPUInfo;
|
||||
bool mUpdateLocals;
|
||||
bool mEvalReady;
|
||||
bool mUpdateMemory;
|
||||
|
||||
QStringList mConsoleOutput;
|
||||
int mBreakPointLine;
|
||||
QString mBreakPointFile;
|
||||
QStringList mLocalsValue;
|
||||
QString mEvalValue;
|
||||
QStringList mMemoryValue;
|
||||
|
||||
bool mStop;
|
||||
// QThread interface
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "gdbmiresultparser.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QList>
|
||||
|
||||
GDBMIResultParser::GDBMIResultParser()
|
||||
|
@ -172,7 +173,7 @@ bool GDBMIResultParser::parseObject(const char *&p, ParseObject &obj)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool GDBMIResultParser::parseArray(const char *&p, QList<GDBMIResultParser::ParseObject> &array)
|
||||
bool GDBMIResultParser::parseArray(const char *&p, QList<GDBMIResultParser::ParseValue> &array)
|
||||
{
|
||||
if (*p!='[')
|
||||
return false;
|
||||
|
@ -180,10 +181,10 @@ bool GDBMIResultParser::parseArray(const char *&p, QList<GDBMIResultParser::Pars
|
|||
if (*p!=']') {
|
||||
while (*p!=0) {
|
||||
skipSpaces(p);
|
||||
ParseObject obj;
|
||||
bool result = parseObject(p,obj);
|
||||
ParseValue val;
|
||||
bool result = parseValue(p,val);
|
||||
if (result) {
|
||||
array.append(obj);
|
||||
array.append(val);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -236,7 +237,7 @@ const QByteArray &GDBMIResultParser::ParseValue::value() const
|
|||
return mValue;
|
||||
}
|
||||
|
||||
const QList<::GDBMIResultParser::ParseObject> &GDBMIResultParser::ParseValue::array() const
|
||||
const QList<::GDBMIResultParser::ParseValue> &GDBMIResultParser::ParseValue::array() const
|
||||
{
|
||||
Q_ASSERT(mType == ParseValueType::Array);
|
||||
return mArray;
|
||||
|
@ -259,6 +260,12 @@ int GDBMIResultParser::ParseValue::intValue(int defaultValue) const
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
QString GDBMIResultParser::ParseValue::pathValue() const
|
||||
{
|
||||
Q_ASSERT(mType == ParseValueType::Value);
|
||||
return QFileInfo(QString::fromLocal8Bit(mValue)).absoluteFilePath();
|
||||
}
|
||||
|
||||
GDBMIResultParser::ParseValueType GDBMIResultParser::ParseValue::type() const
|
||||
{
|
||||
return mType;
|
||||
|
@ -281,7 +288,7 @@ GDBMIResultParser::ParseValue::ParseValue(const ParseObject &object):
|
|||
{
|
||||
}
|
||||
|
||||
GDBMIResultParser::ParseValue::ParseValue(const QList<ParseObject> &array):
|
||||
GDBMIResultParser::ParseValue::ParseValue(const QList<ParseValue> &array):
|
||||
mArray(array),
|
||||
mType(ParseValueType::Array)
|
||||
{
|
||||
|
@ -301,7 +308,7 @@ GDBMIResultParser::ParseValue &GDBMIResultParser::ParseValue::operator=(const Pa
|
|||
mObject = object;
|
||||
}
|
||||
|
||||
GDBMIResultParser::ParseValue &GDBMIResultParser::ParseValue::operator=(const QList<ParseObject>& array)
|
||||
GDBMIResultParser::ParseValue &GDBMIResultParser::ParseValue::operator=(const QList<ParseValue>& array)
|
||||
{
|
||||
Q_ASSERT(mType == ParseValueType::NotAssigned);
|
||||
mType = ParseValueType::Array;
|
||||
|
|
|
@ -48,20 +48,21 @@ public:
|
|||
explicit ParseValue();
|
||||
explicit ParseValue(const QByteArray& value);
|
||||
explicit ParseValue(const ParseObject &object);
|
||||
explicit ParseValue(const QList<ParseObject>& array);
|
||||
explicit ParseValue(const QList<ParseValue>& array);
|
||||
ParseValue(const ParseValue&) = delete;
|
||||
const QByteArray &value() const;
|
||||
const QList<ParseObject> &array() const;
|
||||
const QList<ParseValue> &array() const;
|
||||
const ParseObject &object() const;
|
||||
int intValue(int defaultValue=-1) const;
|
||||
QString pathValue() const;
|
||||
ParseValueType type() const;
|
||||
ParseValue& operator=(const QByteArray& value);
|
||||
ParseValue& operator=(const ParseObject& object);
|
||||
ParseValue& operator=(const QList<ParseObject>& array);
|
||||
ParseValue& operator=(const QList<ParseValue>& array);
|
||||
ParseValue& operator=(const ParseValue& value);
|
||||
private:
|
||||
QByteArray mValue;
|
||||
QList<ParseObject> mArray;
|
||||
QList<ParseValue> mArray;
|
||||
ParseObject mObject;
|
||||
ParseValueType mType;
|
||||
};
|
||||
|
@ -78,7 +79,7 @@ private:
|
|||
bool parseValue(const char* &p, ParseValue& value);
|
||||
bool parseStringValue(const char*&p, QByteArray& stringValue);
|
||||
bool parseObject(const char*&p, ParseObject& obj);
|
||||
bool parseArray(const char*&p, QList<ParseObject>& array);
|
||||
bool parseArray(const char*&p, QList<ParseValue>& array);
|
||||
void skipSpaces(const char* &p);
|
||||
bool isNameChar(char ch);
|
||||
bool isSpaceChar(char ch);
|
||||
|
|
Loading…
Reference in New Issue