/
docNemo
/
clothes-graph
Обзор
Документация
Войти
/
docNemo
/
clothes-graph
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/ClothesGraph.Model/Formulas/Tokenizer.cs
161 строка
6 KB
docNemo
Фундамент продукта: параметрическое построение выкроек, печать, установщик
08 авг 2026, 22:17
08 авг 2026, 22:17
cd47cce
Код
Авторство
О чём код?
using System; using System.Collections.Generic; using System.Globalization; using System.Text; namespace ClothesGraph.Model.Formulas; /// <summary> /// Разбор текста формулы на лексемы. /// </summary> /// <remarks> /// Имена мерок часто содержат пробелы («Обхват груди»), поэтому кроме обычных /// идентификаторов принимается запись в квадратных скобках: <c>[Обхват груди]/4</c>. /// Разделителем дробной части считаются и точка, и запятая — пользователь /// набирает формулу с клавиатуры, а не выбирает из списка. /// </remarks> internal static class Tokenizer { public static List<Token> Tokenize(string text) { var tokens = new List<Token>(); var index = 0; while (index < text.Length) { var symbol = text[index]; if (char.IsWhiteSpace(symbol)) { index++; continue; } // Число начинается либо с цифры, либо с разделителя дробной части, // за которым сразу идёт цифра (запись вида «.5»). Одиночный // разделитель числом не является: это точка свойства или запятая // между аргументами. if (char.IsDigit(symbol) || (IsDecimalSeparator(symbol) && index + 1 < text.Length && char.IsDigit(text[index + 1]))) { tokens.Add(ReadNumber(text, ref index)); continue; } if (symbol == '[') { tokens.Add(ReadBracketedIdentifier(text, ref index)); continue; } if (IsIdentifierStart(symbol)) { tokens.Add(ReadIdentifier(text, ref index)); continue; } var kind = symbol switch { '+' => TokenKind.Plus, '-' => TokenKind.Minus, '*' => TokenKind.Star, '/' => TokenKind.Slash, '^' => TokenKind.Caret, '(' => TokenKind.LeftParenthesis, ')' => TokenKind.RightParenthesis, ',' => TokenKind.Comma, '.' => TokenKind.Dot, _ => throw new FormulaException($"Недопустимый символ «{symbol}»", index) }; tokens.Add(new Token(kind, symbol.ToString(), index, 1)); index++; } tokens.Add(new Token(TokenKind.End, string.Empty, text.Length, 0)); return tokens; } private static bool IsDecimalSeparator(char symbol) => symbol is '.' or ','; private static bool IsIdentifierStart(char symbol) => char.IsLetter(symbol) || symbol == '_'; private static bool IsIdentifierPart(char symbol) => char.IsLetterOrDigit(symbol) || symbol == '_'; private static Token ReadNumber(string text, ref int index) { var start = index; var digits = new StringBuilder(); var separatorSeen = false; while (index < text.Length) { var symbol = text[index]; if (char.IsDigit(symbol)) { digits.Append(symbol); index++; continue; } if (IsDecimalSeparator(symbol)) { // Вторая точка в числе — это уже обращение к свойству // (например 2.Length), разбор числа на этом заканчивается. if (separatorSeen) break; // Разделитель считается дробным, только если следом идёт цифра. // Иначе это точка обращения к свойству (2.длина) либо запятая // между аргументами функции (max(1, 7)). if (index + 1 >= text.Length || !char.IsDigit(text[index + 1])) break; separatorSeen = true; digits.Append('.'); index++; continue; } break; } var literal = digits.ToString(); if (!double.TryParse(literal, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) throw new FormulaException($"Не удалось разобрать число «{literal}»", start, index - start); return new Token(TokenKind.Number, text[start..index], start, index - start, value); } private static Token ReadIdentifier(string text, ref int index) { var start = index; while (index < text.Length && IsIdentifierPart(text[index])) index++; var name = text[start..index]; return new Token(TokenKind.Identifier, name, start, index - start); } private static Token ReadBracketedIdentifier(string text, ref int index) { var start = index; var close = text.IndexOf(']', index + 1); if (close < 0) throw new FormulaException("Не закрыта квадратная скобка", start, text.Length - start); var name = text[(start + 1)..close].Trim(); if (name.Length == 0) throw new FormulaException("Пустое имя в квадратных скобках", start, close - start + 1); index = close + 1; return new Token(TokenKind.Identifier, name, start, index - start); } }