1//===-- InputReader.cpp -----------------------------------------*- C++ -*-===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "lldb/lldb-python.h"
11
12#include <string>
13
14#include "lldb/Core/InputReader.h"
15#include "lldb/Core/Debugger.h"
16#include "lldb/Interpreter/CommandInterpreter.h"
17
18using namespace lldb;
19using namespace lldb_private;
20
21InputReader::InputReader (Debugger &debugger) :
22    m_debugger (debugger),
23    m_callback (NULL),
24    m_callback_baton (NULL),
25    m_end_token (),
26    m_granularity (eInputReaderGranularityInvalid),
27    m_done (true),
28    m_echo (true),
29    m_active (false),
30    m_reader_done (false),
31    m_user_input(),
32    m_save_user_input(false)
33{
34}
35
36InputReader::~InputReader ()
37{
38}
39
40Error
41InputReader::Initialize
42(
43    Callback callback,
44    void *baton,
45    lldb::InputReaderGranularity granularity,
46    const char *end_token,
47    const char *prompt,
48    bool echo
49)
50{
51    Error err;
52    m_callback = callback;
53    m_callback_baton = baton,
54    m_granularity = granularity;
55    if (end_token != NULL)
56        m_end_token = end_token;
57    if (prompt != NULL)
58        m_prompt = prompt;
59    m_done = true;
60    m_echo = echo;
61
62    if (m_granularity == eInputReaderGranularityInvalid)
63    {
64        err.SetErrorString ("Invalid read token size:  Reader must be initialized with a token size other than 'eInputReaderGranularityInvalid'.");
65    }
66    else
67    if (end_token != NULL && granularity != eInputReaderGranularityInvalid)
68    {
69        if (granularity == eInputReaderGranularityByte)
70        {
71            // Check to see if end_token is longer than one byte.
72
73            if (strlen (end_token) > 1)
74            {
75                err.SetErrorString ("Invalid end token:  End token cannot be larger than specified token size (byte).");
76            }
77        }
78        else if (granularity == eInputReaderGranularityWord)
79        {
80            // Check to see if m_end_token contains any white space (i.e. is multiple words).
81
82            const char *white_space = " \t\n";
83            size_t pos = m_end_token.find_first_of (white_space);
84            if (pos != std::string::npos)
85            {
86                err.SetErrorString ("Invalid end token:  End token cannot be larger than specified token size (word).");
87            }
88        }
89        else
90        {
91            // Check to see if m_end_token contains any newlines; cannot handle multi-line end tokens.
92
93            size_t pos = m_end_token.find_first_of ('\n');
94            if (pos != std::string::npos)
95            {
96                err.SetErrorString ("Invalid end token:  End token cannot contain a newline.");
97            }
98        }
99    }
100
101    m_done = err.Fail();
102
103    return err;
104}
105
106size_t
107InputReader::HandleRawBytes (const char *bytes, size_t bytes_len)
108{
109    const char *end_token = NULL;
110
111    if (m_end_token.empty() == false)
112    {
113        end_token = ::strstr (bytes, m_end_token.c_str());
114        if (end_token >= bytes + bytes_len)
115            end_token = NULL;
116    }
117
118    const char *p = bytes;
119    const char *end = bytes + bytes_len;
120
121    switch (m_granularity)
122    {
123    case eInputReaderGranularityInvalid:
124        break;
125
126    case eInputReaderGranularityByte:
127        while (p < end)
128        {
129            if (end_token == p)
130            {
131                p += m_end_token.size();
132                SetIsDone(true);
133                break;
134            }
135
136            if (m_callback (m_callback_baton, *this, eInputReaderGotToken, p, 1) == 0)
137                break;
138            ++p;
139            if (IsDone())
140                break;
141        }
142        // Return how many bytes were handled.
143        return p - bytes;
144        break;
145
146
147    case eInputReaderGranularityWord:
148        {
149            char quote = '\0';
150            const char *word_start = NULL;
151            bool send_word = false;
152            for (; p < end; ++p, send_word = false)
153            {
154                if (end_token && end_token == p)
155                {
156                    m_end_token.size();
157                    SetIsDone(true);
158                    break;
159                }
160
161                const char ch = *p;
162                if (isspace(ch) && (!quote || (quote == ch && p[-1] != '\\')))
163                {
164                    // We have a space character or the terminating quote
165                    send_word = word_start != NULL;
166                    quote = '\0';
167                }
168                else if (quote)
169                {
170                    // We are in the middle of a quoted character
171                    continue;
172                }
173                else if (ch == '"' || ch == '\'' || ch == '`')
174                    quote = ch;
175                else if (word_start == NULL)
176                {
177                    // We have the first character in a word
178                    word_start = p;
179                }
180
181                if (send_word)
182                {
183                    const size_t word_len = p - word_start;
184                    size_t bytes_handled = m_callback (m_callback_baton,
185                                                       *this,
186                                                       eInputReaderGotToken,
187                                                       word_start,
188                                                       word_len);
189
190                    if (bytes_handled != word_len)
191                        return word_start - bytes + bytes_handled;
192
193                    if (IsDone())
194                        return p - bytes;
195                }
196            }
197        }
198        break;
199
200
201    case eInputReaderGranularityLine:
202        {
203            const char *line_start = bytes;
204            const char *end_line = NULL;
205            while (p < end)
206            {
207                const char ch = *p;
208                if (ch == '\n' || ch == '\r')
209                {
210                    size_t line_length = p - line_start;
211                    // Now skip the newline character
212                    ++p;
213                    // Skip a complete DOS newline if we run into one
214                    if (ch == 0xd && p < end && *p == 0xa)
215                        ++p;
216
217                    if (line_start <= end_token && end_token < line_start + line_length)
218                    {
219                        SetIsDone(true);
220                        m_callback (m_callback_baton,
221                                    *this,
222                                    eInputReaderGotToken,
223                                    line_start,
224                                    end_token - line_start);
225
226                        return p - bytes;
227                    }
228
229                    size_t bytes_handled = m_callback (m_callback_baton,
230                                                       *this,
231                                                       eInputReaderGotToken,
232                                                       line_start,
233                                                       line_length);
234
235                    end_line = p;
236
237                    if (bytes_handled != line_length)
238                    {
239                        // The input reader wasn't able to handle all the data
240                        return line_start - bytes + bytes_handled;
241                    }
242
243
244                    if (IsDone())
245                        return p - bytes;
246
247                    line_start = p;
248                }
249                else
250                {
251                    ++p;
252                }
253            }
254
255            if (end_line)
256                return end_line - bytes;
257        }
258        break;
259
260
261    case eInputReaderGranularityAll:
262        {
263            // Nothing should be handle unless we see our end token
264            if (end_token)
265            {
266                size_t length = end_token - bytes;
267                size_t bytes_handled = m_callback (m_callback_baton,
268                                                   *this,
269                                                   eInputReaderGotToken,
270                                                   bytes,
271                                                   length);
272                m_done = true;
273
274                p += bytes_handled + m_end_token.size();
275
276                // Consume any white space, such as newlines, beyond the end token
277
278                while (p < end && isspace(*p))
279                    ++p;
280
281                if (bytes_handled != length)
282                    return bytes_handled;
283                else
284                {
285                    return p - bytes;
286                    //return bytes_handled + m_end_token.size();
287                }
288            }
289            return 0;
290        }
291        break;
292    }
293    return 0;
294}
295
296const char *
297InputReader::GetPrompt () const
298{
299    if (!m_prompt.empty())
300        return m_prompt.c_str();
301    else
302        return NULL;
303}
304
305void
306InputReader::RefreshPrompt ()
307{
308	if (m_debugger.GetCommandInterpreter().GetBatchCommandMode())
309        return;
310
311    if (!m_prompt.empty())
312    {
313        File &out_file = m_debugger.GetOutputFile();
314        if (out_file.IsValid())
315        {
316            out_file.Printf ("%s", m_prompt.c_str());
317            out_file.Flush();
318        }
319    }
320}
321
322void
323InputReader::Notify (InputReaderAction notification)
324{
325    switch (notification)
326    {
327    case eInputReaderActivate:
328    case eInputReaderReactivate:
329        m_active = true;
330        m_reader_done.SetValue(false, eBroadcastAlways);
331        break;
332
333    case eInputReaderDeactivate:
334    case eInputReaderDone:
335        m_active = false;
336        break;
337
338    case eInputReaderAsynchronousOutputWritten:
339        break;
340
341    case eInputReaderInterrupt:
342    case eInputReaderEndOfFile:
343        break;
344
345    case eInputReaderGotToken:
346        return; // We don't notify the tokens here, it is done in HandleRawBytes
347    }
348    if (m_callback)
349        m_callback (m_callback_baton, *this, notification, NULL, 0);
350    if (notification == eInputReaderDone)
351        m_reader_done.SetValue(true, eBroadcastAlways);
352}
353
354void
355InputReader::WaitOnReaderIsDone ()
356{
357    m_reader_done.WaitForValueEqualTo (true);
358}
359
360const char *
361InputReader::GranularityAsCString (lldb::InputReaderGranularity granularity)
362{
363    switch (granularity)
364    {
365    case eInputReaderGranularityInvalid:  return "invalid";
366    case eInputReaderGranularityByte:     return "byte";
367    case eInputReaderGranularityWord:     return "word";
368    case eInputReaderGranularityLine:     return "line";
369    case eInputReaderGranularityAll:      return "all";
370    }
371
372    static char unknown_state_string[64];
373    snprintf(unknown_state_string, sizeof (unknown_state_string), "InputReaderGranularity = %i", granularity);
374    return unknown_state_string;
375}
376
377bool
378InputReader::HandlerData::GetBatchMode()
379{
380    return reader.GetDebugger().GetCommandInterpreter().GetBatchCommandMode();
381}
382
383lldb::StreamSP
384InputReader::HandlerData::GetOutStream()
385{
386    return reader.GetDebugger().GetAsyncOutputStream();
387}
388