Skip to content

Commit 0f46f06

Browse files
authored
Merge pull request #1636 from Mr-Rm/v2/fix-stringop
V2: совместимость и оптимизация операций со строками
2 parents 682f5ae + 95bb95d commit 0f46f06

File tree

2 files changed

+154
-39
lines changed

2 files changed

+154
-39
lines changed

src/OneScript.StandardLibrary/StringOperations.cs

Lines changed: 84 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,35 @@ This Source Code Form is subject to the terms of the
55
at http://mozilla.org/MPL/2.0/.
66
----------------------------------------------------------*/
77

8-
using System;
9-
using System.Linq;
108
using OneScript.Commons;
119
using OneScript.Contexts;
1210
using OneScript.Contexts.Enums;
1311
using OneScript.Exceptions;
1412
using OneScript.Execution;
13+
using OneScript.Localization;
1514
using OneScript.StandardLibrary.Collections;
1615
using ScriptEngine.Machine;
1716
using ScriptEngine.Machine.Contexts;
17+
using System;
18+
using System.Linq;
1819

1920
namespace OneScript.StandardLibrary
2021
{
2122
[GlobalContext(Category = "Операции со строками")]
2223
public class StringOperations : GlobalContextBase<StringOperations>
2324
{
25+
private static readonly System.Text.RegularExpressions.Regex _templateRe
26+
= new System.Text.RegularExpressions.Regex(@"(%%)|%(\d+)|%\((\d+)\)|%",
27+
System.Text.RegularExpressions.RegexOptions.Compiled);
28+
2429
/// <summary>
2530
/// Получает строку на языке, заданном во втором параметре (коды языков в соответствии с ISO 639-1)
2631
/// или на текущем языке системы.
2732
/// </summary>
2833
/// <param name="src">Строка на нескольких языках</param>
2934
/// <param name="lang">Код языка (если не указан, возвращает вариант для текущего языка системы,
30-
/// если вариант не найден, то возвращает вариант для английского языка, если не задан вариант для английского языка,
31-
/// то возвращает первый вариант из списка)</param>
35+
/// если вариант не найден, то возвращает вариант для английского языка,
36+
/// если не задан вариант для английского языка, то возвращает первый вариант из списка)</param>
3237
[ContextMethod("НСтр", "NStr")]
3338
public string NStr(string src, string lang = null)
3439
{
@@ -39,19 +44,20 @@ public string NStr(string src, string lang = null)
3944
/// Определяет, что строка начинается с указанной подстроки.
4045
/// </summary>
4146
/// <param name="inputString">Строка, начало которой проверяется на совпадение с подстрокой поиска.</param>
42-
/// <param name="searchString">Строка, содержащая предполагаемое начало строки. В случае если переданное значение является пустой строкой генерируется исключительная ситуация.</param>
47+
/// <param name="searchString">Строка, содержащая предполагаемое начало строки.
48+
/// В случае, если переданное значение является пустой строкой, генерируется исключительная ситуация.</param>
4349
[ContextMethod("СтрНачинаетсяС", "StrStartsWith")]
4450
public bool StrStartsWith(string inputString, string searchString)
4551
{
4652
bool result = false;
4753

48-
if(!string.IsNullOrEmpty(inputString))
54+
if (!string.IsNullOrEmpty(inputString))
4955
{
5056
if (!string.IsNullOrEmpty(searchString))
5157
{
5258
result = inputString.StartsWith(searchString);
5359
}
54-
else throw new RuntimeException("Ошибка при вызове метода контекста (СтрНачинаетсяС): Недопустимое значение параметра (параметр номер '2')");
60+
else throw StringOpException.StrStartsWith();
5561
}
5662

5763
return result;
@@ -61,19 +67,20 @@ public bool StrStartsWith(string inputString, string searchString)
6167
/// Определяет, заканчивается ли строка указанной подстрокой.
6268
/// </summary>
6369
/// <param name="inputString">Строка, окончание которой проверяется на совпадение с подстрокой поиска.</param>
64-
/// <param name="searchString">Строка, содержащая предполагаемое окончание строки. В случае если переданное значение является пустой строкой генерируется исключительная ситуация.</param>
70+
/// <param name="searchString">Строка, содержащая предполагаемое окончание строки.
71+
/// В случае, если переданное значение является пустой строкой, генерируется исключительная ситуация.</param>
6572
[ContextMethod("СтрЗаканчиваетсяНа", "StrEndsWith")]
6673
public bool StrEndsWith(string inputString, string searchString)
6774
{
6875
bool result = false;
6976

70-
if(!string.IsNullOrEmpty(inputString))
77+
if (!string.IsNullOrEmpty(inputString))
7178
{
7279
if (!string.IsNullOrEmpty(searchString))
7380
{
7481
result = inputString.EndsWith(searchString);
7582
}
76-
else throw new RuntimeException("Ошибка при вызове метода контекста (СтрЗаканчиваетсяНа): Недопустимое значение параметра (параметр номер '2')");
83+
else throw StringOpException.StrEndsWith();
7784
}
7885

7986
return result;
@@ -84,22 +91,25 @@ public bool StrEndsWith(string inputString, string searchString)
8491
/// </summary>
8592
/// <param name="inputString">Разделяемая строка.</param>
8693
/// <param name="stringDelimiter">Строка символов, каждый из которых является индивидуальным разделителем.</param>
87-
/// <param name="includeEmpty">Указывает необходимость включать в результат пустые строки, которые могут образоваться в результате разделения исходной строки. Значение по умолчанию: Истина. </param>
94+
/// <param name="includeEmpty">Указывает необходимость включать в результат пустые строки,
95+
/// которые могут образоваться в результате разделения исходной строки. Значение по умолчанию: Истина. </param>
8896
[ContextMethod("СтрРазделить", "StrSplit")]
8997
public ArrayImpl StrSplit(string inputString, string stringDelimiter, bool? includeEmpty = true)
9098
{
9199
string[] arrParsed;
92100
if (includeEmpty == null)
93-
includeEmpty = true;
94-
95-
if(!string.IsNullOrEmpty(inputString))
101+
includeEmpty = true;
102+
103+
if (!string.IsNullOrEmpty(inputString))
96104
{
97-
arrParsed = inputString.Split(stringDelimiter?.ToCharArray(), (bool) includeEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries);
105+
arrParsed = inputString.Split(stringDelimiter?.ToCharArray(),
106+
(bool)includeEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries);
98107
}
99108
else
100109
{
101-
arrParsed = (bool) includeEmpty ? new string[] { string.Empty } : new string[0];
110+
arrParsed = (bool)includeEmpty ? new string[] { string.Empty } : Array.Empty<string>();
102111
}
112+
103113
return new ArrayImpl(arrParsed.Select(x => ValueFactory.Create(x)));
104114
}
105115

@@ -111,8 +121,8 @@ public ArrayImpl StrSplit(string inputString, string stringDelimiter, bool? incl
111121
[ContextMethod("СтрСоединить", "StrConcat")]
112122
public string StrConcat(IBslProcess process, ArrayImpl input, string delimiter = null)
113123
{
114-
var strings = input.Select(x => x.AsString(process));
115-
124+
var strings = input.Select(x => x.AsString(process));
125+
116126
return String.Join(delimiter, strings);
117127
}
118128

@@ -138,24 +148,28 @@ public int StrCompare(string first, string second)
138148
/// <param name="occurance">Указывает номер вхождения искомой подстроки в исходной строке</param>
139149
/// <returns>Позицию искомой строки в исходной строке. Возвращает 0, если подстрока не найдена.</returns>
140150
[ContextMethod("СтрНайти", "StrFind")]
141-
public int StrFind(string haystack, string needle, SearchDirection direction = SearchDirection.FromBegin, int startPos = 0, int occurance = 0)
151+
public int StrFind(string haystack, string needle, SearchDirection direction = SearchDirection.FromBegin, int startPos = 0, int occurance = 1)
142152
{
143-
int len = haystack.Length;
144-
if (len == 0 || needle.Length == 0)
153+
if (needle == null || needle.Length == 0)
154+
return 1;
155+
156+
int len = haystack?.Length ?? 0;
157+
if (len == 0)
145158
return 0;
146159

147160
bool fromBegin = direction == SearchDirection.FromBegin;
148161

149-
if(startPos == 0)
162+
if (startPos == 0)
150163
{
151164
startPos = fromBegin ? 1 : len;
152165
}
166+
else if (startPos < 1 || startPos > len)
167+
throw RuntimeException.InvalidNthArgumentValue(4);
153168

154-
if (startPos < 1 || startPos > len)
155-
throw RuntimeException.InvalidArgumentValue();
156-
157-
if (occurance == 0)
158-
occurance = 1;
169+
if (occurance < 0)
170+
return 0;
171+
else if (occurance == 0)
172+
throw RuntimeException.InvalidNthArgumentValue(5);
159173

160174
int startIndex = startPos - 1;
161175
int foundTimes = 0;
@@ -174,7 +188,6 @@ public int StrFind(string haystack, string needle, SearchDirection direction = S
174188
if (startIndex >= len)
175189
break;
176190
}
177-
178191
}
179192
else
180193
{
@@ -189,13 +202,9 @@ public int StrFind(string haystack, string needle, SearchDirection direction = S
189202
if (startIndex < 0)
190203
break;
191204
}
192-
193205
}
194206

195-
if (foundTimes == occurance)
196-
return index + 1;
197-
else
198-
return 0;
207+
return foundTimes == occurance ? index + 1 : 0;
199208
}
200209

201210
/// <summary>
@@ -216,9 +225,8 @@ public string StrTemplate(string template,
216225
.SkipWhile(x => x == null)
217226
.Count();
218227

219-
var re = new System.Text.RegularExpressions.Regex(@"(%%)|%(\d+)|%\((\d+)\)|%");
220228
int maxNumber = 0;
221-
var result = re.Replace(srcFormat, (m) =>
229+
var result = _templateRe.Replace(srcFormat, (m) =>
222230
{
223231
if (m.Groups[1].Success)
224232
return "%";
@@ -228,7 +236,7 @@ public string StrTemplate(string template,
228236
var number = int.Parse(m.Groups[2].Success ? m.Groups[2].Value : m.Groups[3].Value);
229237

230238
if (number < 1 || number > 10)
231-
throw new RuntimeException($"Ошибка синтаксиса шаблона в позиции {m.Index+2}: недопустимый номер подстановки");
239+
throw StringOpException.TemplateSubst(m.Index + 2, number);
232240

233241
//FIXME: отключено, т.к. платформа игнорирует ошибку с недостаточным числом параметров
234242
//if (number > passedArgsCount)
@@ -240,7 +248,7 @@ public string StrTemplate(string template,
240248
return arguments[10-number] ?? "";
241249
}
242250

243-
throw new RuntimeException("Ошибка синтаксиса шаблона в позиции " + (m.Index + 2));
251+
throw StringOpException.TemplateSyntax(m.Index + 2);
244252
});
245253

246254
if (passedArgsCount > maxNumber)
@@ -253,9 +261,9 @@ public static IAttachableContext CreateInstance()
253261
{
254262
return new StringOperations();
255263
}
256-
257264
}
258265

266+
259267
[EnumerationType("НаправлениеПоиска", "SearchDirection")]
260268
public enum SearchDirection
261269
{
@@ -265,5 +273,42 @@ public enum SearchDirection
265273
FromEnd
266274
}
267275

268-
276+
277+
public class StringOpException : RuntimeException
278+
{
279+
public StringOpException(BilingualString message, Exception innerException) : base(message,
280+
innerException)
281+
{}
282+
283+
public StringOpException(BilingualString message) : base(message)
284+
{}
285+
286+
public static StringOpException StrStartsWith()
287+
{
288+
return new StringOpException(new BilingualString(
289+
"Ошибка при вызове метода контекста (СтрНачинаетсяС): Недопустимое значение параметра номер '2'",
290+
"Error calling context method (StrStartsWith): Invalid parameter number '2' value"));
291+
}
292+
public static StringOpException StrEndsWith()
293+
{
294+
return new StringOpException(new BilingualString(
295+
"Ошибка при вызове метода контекста (СтрЗаканчиваетсяНа): Недопустимое значение параметра номер '2'",
296+
"Error calling context method (StrEndsWith): Invalid parameter number '2' value"));
297+
}
298+
299+
public static StringOpException TemplateSyntax(int pos)
300+
{
301+
return new StringOpException(new BilingualString(
302+
$"Ошибка синтаксиса шаблона в позиции {pos}",
303+
$"Template syntax error at position {pos}"));
304+
}
305+
306+
public static StringOpException TemplateSubst(int pos, int num)
307+
{
308+
return new StringOpException(new BilingualString(
309+
$"Ошибка синтаксиса шаблона в позиции {pos}. Недопустимый номер подстановки: '{num}'",
310+
$"Template syntax error at position {pos}. Invalid substitution number: '{num}'"));
311+
}
312+
}
313+
269314
}

tests/stringoperations.os

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@
4848
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_ВтороеВхождениеСНачала");
4949
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_ВтороеВхождениеСКонца");
5050
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_ВтороеВхождениеСКонцаНачинаяСПредпоследнегоСимвола");
51+
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_СПустойСтрокой");
52+
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_СПустойПодстрокойПоиска");
53+
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_СПропущеннымиСтроками");
54+
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_СНевернойНачальнойПозицией");
55+
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_СНулевымЧисломВхождений");
56+
ВсеТесты.Добавить("ТестДолжен_ВызватьМетод_СтрНайти_СОтрицательнымЧисломВхождений");
57+
5158
ВсеТесты.Добавить("Тест_ДолженПроверитьНСтрВозвращаетПервуюСтроку");
5259
ВсеТесты.Добавить("ТестДолжен_Проверить_Что_НСТР_С_СуществующимПараметром_ВозвращаетНужнуюСтроку");
5360
ВсеТесты.Добавить("ТестДолжен_Проверить_Что_НСТР_С_НесуществующимПараметром_ВозвращаетПустуюСтроку");
@@ -459,6 +466,69 @@
459466

460467
КонецПроцедуры
461468

469+
Процедура ТестДолжен_ВызватьМетод_СтрНайти_СПустойСтрокой() Экспорт
470+
471+
ГдеИщем = "";
472+
ЧтоИщем = ",";
473+
474+
юТест.ПроверитьРавенство(0, СтрНайти(ГдеИщем, ЧтоИщем));
475+
476+
КонецПроцедуры
477+
478+
Процедура ТестДолжен_ВызватьМетод_СтрНайти_СПустойПодстрокойПоиска() Экспорт
479+
480+
ГдеИщем = "Один,Два,Три,";
481+
ЧтоИщем = "";
482+
483+
юТест.ПроверитьРавенство(1, СтрНайти(ГдеИщем, ЧтоИщем));
484+
485+
КонецПроцедуры
486+
487+
Процедура ТестДолжен_ВызватьМетод_СтрНайти_СПропущеннымиСтроками() Экспорт
488+
юТест.ПроверитьРавенство(1, СтрНайти(,));
489+
КонецПроцедуры
490+
491+
Процедура ТестДолжен_ВызватьМетод_СтрНайти_СНевернойНачальнойПозицией() Экспорт
492+
493+
ГдеИщем = "Один,Два,Три,";
494+
ЧтоИщем = ",";
495+
496+
Попытка
497+
СтрНайти(ГдеИщем, ЧтоИщем, , 999);
498+
Исключение
499+
юТест.ТестПройден();
500+
Возврат;
501+
КонецПопытки;
502+
503+
юТест.ТестПровален("Ожидаемое исключение не возникло.");
504+
505+
КонецПроцедуры
506+
507+
Процедура ТестДолжен_ВызватьМетод_СтрНайти_СНулевымЧисломВхождений() Экспорт
508+
509+
ГдеИщем = "Один,Два,Три,";
510+
ЧтоИщем = ",";
511+
512+
Попытка
513+
СтрНайти(ГдеИщем, ЧтоИщем,,, 0);
514+
Исключение
515+
юТест.ТестПройден();
516+
Возврат;
517+
КонецПопытки;
518+
519+
юТест.ТестПровален("Ожидаемое исключение не возникло.");
520+
521+
КонецПроцедуры
522+
523+
Процедура ТестДолжен_ВызватьМетод_СтрНайти_СОтрицательнымЧисломВхождений() Экспорт
524+
525+
ГдеИщем = "Один,Два,Три,";
526+
ЧтоИщем = ",";
527+
528+
юТест.ПроверитьРавенство(0, СтрНайти(ГдеИщем, ЧтоИщем,,, -2));
529+
530+
КонецПроцедуры
531+
462532
Процедура Тест_ДолженПроверитьНСтрВозвращаетПервуюСтроку() Экспорт
463533

464534
Стр = НСтр("ru = 'Строка1'; en = 'Строка2'", "ru");

0 commit comments

Comments
 (0)