ФОРМУЛЫ АРАБСКИХ ПАРСОВ

Открой рецепты арабских парсов, которые превращают кожу в шелк и волосы в каскад золота

Раз, два, три — парсим, как арабские математики XVIII века!

Именно введение арабских цифр и позиционной системы записи позволило Европе перейти от абака к алгоритмам, способным решать задачи в письменном виде. Сегодня мы проделаем аналогичный путь: избавимся от «абаковых» ручных вычислений и научимся парсить формулы арабской записи с помощью элегантных алгоритмов.

Что такое арабский парсинг?

Парсер — это программа, которая анализирует текстовые данные и извлекает структурированную информацию. В контексте «арабского парсинга» речь идёт о двух вещах:

  • Обработка чисел в арабской системе счисления.
  • Корректное отображение смешанного текста (арабско-латинского) с учётом направления письма справа налево (RTL).

Этапы парсинга формул

Лексический анализ (токенизация)

На этом этапе входная строка разбивается на лексемы, минимальные неделимые единицы:

Вход: "٣(١+٢)+√١٦"
Токены: [ '٣', '', '(', '١', '+', '٢', ')', '+', '√', '١٦' ]

Синтаксический анализ (построение дерева)

Используем LL(1) или LR(1) грамматику, описанную в BNF:

Expr ::= Term ('+'|'-') Expr | Term
Term ::= Factor ('*'|'/') Term | Factor
Factor::= NUMBER | '(' Expr ')' | '√' Factor

Учёт Unicode BiDi-алгоритма

Арабские цифры и математические знаки сохраняют нейтральное направление, но вставки английских переменных force-LTR требуют явного указания направления символом ‎ или U+202A.
Пример:

"النتيجة = ١٠ + 2x ‎ + ٣"

Алгоритм «Арабский парсер» на Python

Шаг 1. Импорт библиотек


from typing import List, Union
import unicodedata

Шаг 2. Лексер


def tokenize(expr: str) -> List[str]:
 tokens = []
 i = 0
 while i < len(expr):
 c = expr[i]
 if c in "٠١٢٣٤٥٦٧٨٩0123456789":
 num = c
 while i+1 < len(expr) and expr[i+1] in "٠١٢٣٤٥٦٧٨٩0123456789":
 i += 1
 num += expr[i]
 tokens.append(str(int(num.translate(str.maketrans("٠١٢٣٤٥٦٧٨٩", "0123456789")))))
 elif c in "+-*/√":
 tokens.append(c)
 elif not c.isspace:
 raise ValueError(f"Неизвестный символ {c}")
 i += 1
 return tokens

Шаг 3. Парсер-рекурсивный спуск


class ArabParser:
 def __init__(self, tokens: List[str]):
 self.tokens = tokens
 self.pos = 0

 def parse(self) -> float:
 return self.expr

 def expr(self):
 val = self.term
 while self.current in ('+', '-'):
 op = self.consume
 right = self.term
 val = val + right if op == '+' else val ― right
 return val

 def term(self):
 val = self.factor
 while self.current in ('', '/'):
 op = self.consume
 right = self.factor
 val = val  right if op == '*' else val / right
 return val

 def factor(self):
 if self.current == '(':
 self.consume
 val = self.expr
 assert self.consume == ')'
 return val
 elif self.current == '√':
 self.consume
 return self.factor ** 0.5
 else:
 return float(self.consume)

 def current(self):
 return self.tokens[self.pos] if self.pos < len(self.tokens) else None

 def consume(self):
 tok = self.current
 self.pos += 1
 return tok

Пробуем в деле


tokens = tokenize("√١٦ + ٣ * (١+٢)")
print(ArabParser(tokens).parse) # 11.0

Советы по RTL-вёрстке

  • Используйте CSS direction: rtl; для контейнера с арабским текстом.
  • Для математических формул применяйте flexbox с направлением ltr, чтобы знаки «+» и «=» выглядели естественно.
  • Добавляйте невидимые маркеры направления Unicode (U+200E, U+202A) при смешанном контенте.

Как и алгоритмики средневековой Европы, мы победили «абак» ручного разбора формул, создав компактный арабский парсер. Он умеет:

  1. Понимать арабские и латинские цифры в одном выражении.
  2. Корректно обрабатывать смешанное направление текста.
  3. Выдавать результат в любом машинно-читаемом формате: .txt, .json, .sql.

Устраивайтесь поудобнее, запускайте скрипт, и пусть ваши данные потекут, как арабские цифры в XVIII веке: быстро, точно и без абака!