1/*
2 * Copyright 2007-2012, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <ArgumentVector.h>
8
9#include <stdlib.h>
10#include <string.h>
11
12#include <string>
13#include <vector>
14
15
16struct ArgumentVector::Parser {
17	ParseError Parse(const char* commandLine, const char*& _errorLocation)
18	{
19		// init temporary arg/argv storage
20		fCurrentArg.clear();
21		fCurrentArgStarted = false;
22		fArgVector.clear();
23		fTotalStringSize = 0;
24
25		for (; *commandLine; commandLine++) {
26			char c = *commandLine;
27
28			// whitespace delimits args and is otherwise ignored
29			if (isspace(c)) {
30				_PushCurrentArg();
31				continue;
32			}
33
34			const char* errorBase = commandLine;
35
36			switch (c) {
37				case '\'':
38					// quoted string -- no quoting
39					while (*++commandLine != '\'') {
40						c = *commandLine;
41						if (c == '\0') {
42							_errorLocation = errorBase;
43							return UNTERMINATED_QUOTED_STRING;
44						}
45						_PushCharacter(c);
46					}
47					break;
48
49				case '"':
50					// quoted string -- some quoting
51					while (*++commandLine != '"') {
52						c = *commandLine;
53						if (c == '\0') {
54							_errorLocation = errorBase;
55							return UNTERMINATED_QUOTED_STRING;
56						}
57
58						if (c == '\\') {
59							c = *++commandLine;
60							if (c == '\0') {
61								_errorLocation = errorBase;
62								return UNTERMINATED_QUOTED_STRING;
63							}
64
65							// only '\' and '"' can be quoted, otherwise the
66							// the '\' is treated as a normal char
67							if (c != '\\' && c != '"')
68								_PushCharacter('\\');
69						}
70
71						_PushCharacter(c);
72					}
73					break;
74
75				case '\\':
76					// quoted char
77					c = *++commandLine;
78					if (c == '\0') {
79						_errorLocation = errorBase;
80						return TRAILING_BACKSPACE;
81					}
82					_PushCharacter(c);
83					break;
84
85				default:
86					// normal char
87					_PushCharacter(c);
88					break;
89			}
90		}
91
92		// commit last arg
93		_PushCurrentArg();
94
95		return NO_ERROR;
96	}
97
98	const std::vector<std::string>& ArgVector() const
99	{
100		return fArgVector;
101	}
102
103	size_t TotalStringSize() const
104	{
105		return fTotalStringSize;
106	}
107
108private:
109	void _PushCurrentArg()
110	{
111		if (fCurrentArgStarted) {
112			fArgVector.push_back(fCurrentArg);
113			fTotalStringSize += fCurrentArg.length() + 1;
114			fCurrentArgStarted = false;
115		}
116	}
117
118	void _PushCharacter(char c)
119	{
120		if (!fCurrentArgStarted) {
121			fCurrentArg = "";
122			fCurrentArgStarted = true;
123		}
124
125		fCurrentArg += c;
126	}
127
128private:
129	// temporaries
130	std::string					fCurrentArg;
131	bool						fCurrentArgStarted;
132	std::vector<std::string>	fArgVector;
133	size_t						fTotalStringSize;
134};
135
136
137ArgumentVector::ArgumentVector()
138	:
139	fArguments(NULL),
140	fCount(0)
141{
142}
143
144
145ArgumentVector::~ArgumentVector()
146{
147	free(fArguments);
148}
149
150
151char**
152ArgumentVector::DetachArguments()
153{
154	char** arguments = fArguments;
155	fArguments = NULL;
156	fCount = 0;
157	return arguments;
158}
159
160
161ArgumentVector::ParseError
162ArgumentVector::Parse(const char* commandLine, const char** _errorLocation)
163{
164	free(DetachArguments());
165
166	ParseError error;
167	const char* errorLocation = commandLine;
168
169	try {
170		Parser parser;
171		error = parser.Parse(commandLine, errorLocation);
172
173		if (error == NO_ERROR) {
174			// Create a char* array and copy everything into a single
175			// allocation.
176			int count = parser.ArgVector().size();
177			size_t arraySize = (count + 1) * sizeof(char*);
178			fArguments = (char**)malloc(
179				arraySize + parser.TotalStringSize());
180			if (fArguments != 0) {
181				char* argument = (char*)(fArguments + count + 1);
182				for (int i = 0; i < count; i++) {
183					fArguments[i] = argument;
184					const std::string& sourceArgument = parser.ArgVector()[i];
185					size_t argumentSize = sourceArgument.length() + 1;
186					memcpy(argument, sourceArgument.c_str(), argumentSize);
187					argument += argumentSize;
188				}
189
190				fArguments[count] = NULL;
191				fCount = count;
192			} else
193				error = NO_MEMORY;
194		}
195	} catch (...) {
196		error = NO_MEMORY;
197	}
198
199	if (error != NO_ERROR && _errorLocation != NULL)
200		*_errorLocation = errorLocation;
201
202	return error;
203}
204