strformat
Строковая интерполяция / форматирование, вдохновлённая f-строками Python.
fmt vs. &
Вы можете использовать fmt или унарный оператор & для форматирования. Разница между ними тонкая, но важная.
Синтаксис fmt"{expr}" более эстетичен, но скрывает небольшую особенность. Строка является обобщённой строкой-сырью. Это имеет некоторые неожиданные последствия:
import strformat
let msg = "hello"
doAssert fmt"{msg}\n" == "hello\\n"
Поскольку литерал — это строковый литерал-сырьё, \n не интерпретируется как последовательность escape.
Существует несколько способов обойти это, включая использование оператора &:
import strformat
let msg = "hello"
doAssert &"{msg}\n" == "hello\n"
doAssert fmt"{msg}{'\n'}" == "hello\n"
doAssert fmt("{msg}\n") == "hello\n"
doAssert "{msg}\n".fmt == "hello\n"
Выбор стиля зависит от вас.
Форматирование строк
import strformat
doAssert &"""{"abc":>4}""" == " abc"
doAssert &"""{"abc":<4}""" == "abc " Форматирование чисел с плавающей точкой
import strformat
doAssert fmt"{-12345:08}" == "-0012345"
doAssert fmt"{-1:3}" == " -1"
doAssert fmt"{-1:03}" == "-01"
doAssert fmt"{16:#X}" == "0x10"
doAssert fmt"{123.456}" == "123.456"
doAssert fmt"{123.456:>9.3f}" == " 123.456"
doAssert fmt"{123.456:9.3f}" == " 123.456"
doAssert fmt"{123.456:9.4f}" == " 123.4560"
doAssert fmt"{123.456:>9.0f}" == " 123."
doAssert fmt"{123.456:<9.4f}" == "123.4560 "
doAssert fmt"{123.456:e}" == "1.234560e+02"
doAssert fmt"{123.456:>13e}" == " 1.234560e+02"
doAssert fmt"{123.456:13e}" == " 1.234560e+02" Отладка строк
fmt"{expr=}" расширяется до fmt"expr={expr}", а именно текста выражения, знака равенства и результатов вычисления выражения.
import strformat
doAssert fmt"{123.456=}" == "123.456=123.456"
doAssert fmt"{123.456=:>9.3f}" == "123.456= 123.456"
let x = "hello"
doAssert fmt"{x=}" == "x=hello"
doAssert fmt"{x =}" == "x =hello"
let y = 3.1415926
doAssert fmt"{y=:.2f}" == fmt"y={y:.2f}"
doAssert fmt"{y=}" == fmt"y={y}"
doAssert fmt"{y = : <8}" == fmt"y = 3.14159 "
proc hello(a: string, b: float): int = 12
let a = "hello"
let b = 3.1415926
doAssert fmt"{hello(x, y) = }" == "hello(x, y) = 12"
doAssert fmt"{x.hello(y) = }" == "x.hello(y) = 12"
doAssert fmt"{hello x, y = }" == "hello x, y = 12"
Обратите внимание, что важна расстановка пробелов:
import strformat
let x = "12"
doAssert fmt"{x=}" == "x=12"
doAssert fmt"{x =:}" == "x =12"
doAssert fmt"{x =}" == "x =12"
doAssert fmt"{x= :}" == "x= 12"
doAssert fmt"{x= }" == "x= 12"
doAssert fmt"{x = :}" == "x = 12"
doAssert fmt"{x = }" == "x = 12"
doAssert fmt"{x = :}" == "x = 12"
doAssert fmt"{x = }" == "x = 12" Детали реализации
Выражение, такое как &"{key} is {value:arg} {{z}}" преобразуется в:
var temp = newStringOfCap(educatedCapGuess)
temp.formatValue key, ""
temp.add " is "
temp.formatValue value, arg
temp.add " {z}"
temp
Части строки, заключённые в фигурные скобки, интерпретируются как код Nim. Чтобы экранировать { или } символ, удвойте его.
& делегирует большую часть работы открытому перегруженному набору formatValue процедур. Обычно требуемая подпись для типа T , поддерживающего форматирование, выглядит как proc formatValue(result: var string; x: T; specifier: string).
Подвыражение после двоеточия (arg в &"{key} is {value:arg} {{z}}") необязательно. Оно будет передано как последний аргумент в formatValue. Если двоеточие с подвыражением опущены, вместо этого будет передана пустая строка.
Для строк и числовых типов необязательный аргумент является так называемым "стандартным спецификатором формата".
Стандартный спецификатор формата для строк, целых и чисел с плавающей точкой
Общая форма стандартного спецификатора формата:
[[fill]align][sign][#][0][minimumwidth][.precision][type]
Квадратные скобки [] обозначают необязательный элемент.
Необязательный флаг выравнивания может быть одним из следующих:
- '<'
- Принудительно выравнивает поле влево в доступном пространстве. (По умолчанию для строк.)
- '>'
- Принудительно выравнивает поле вправо в доступном пространстве. (По умолчанию для чисел.)
- '^'
- Принудительно выравнивает поле по центру в доступном пространстве.
Обратите внимание, что если минимальная ширина поля не определена, ширина поля всегда будет такой же, как и данные, которые её заполняют, поэтому в этом случае опция выравнивания не имеет смысла.
Необязательный символ "fill" определяет символ, используемый для заполнения поля до минимальной ширины. Если символ fill присутствует, за ним должен следовать флаг выравнивания.
Опция 'sign' действительна только для числовых типов и может быть одной из следующих:
| Знак | Значение |
|---|---|
+ |
Указывает, что знак должен использоваться как для положительных, так и для отрицательных чисел. |
- |
Указывает, что знак должен использоваться только для отрицательных чисел (по умолчанию). |
| (пробел) | Указывает, что ведущий пробел должен использоваться для положительных чисел. |
Если присутствует символ '#', целые числа используют "альтернативную форму" форматирования. Это означает, что двоичные, восьмеричные и шестнадцатеричные выводы будут префиксны соответственно '0b', '0o' и '0x'.
'width' — это десятичное целое число, определяющее минимальную ширину поля. Если не указано, ширина поля определяется содержимым.
Если поле ширины предваряется нулём ('0'), это включает нулевое заполнение.
'precision' — это десятичное число, указывающее, сколько цифр должно отображаться после десятичной точки при преобразовании с плавающей точкой. Для нечисловых типов поле указывает максимальный размер поля — другими словами, сколько символов будет использовано из содержимого поля. Точность игнорируется при преобразованиях целых чисел.
Наконец, 'type' определяет, как должны быть представлены данные.
Доступные типы представления целых чисел:
| Тип | Результат |
|---|---|
b |
Двоичное. Выводит число в системе счисления по основанию 2. |
d |
Десятичное целое число. Выводит число в системе счисления по основанию 10. |
o |
Восьмеричный формат. Выводит число в системе счисления по основанию 8. |
x |
Шестнадцатеричный формат. Выводит число в системе счисления по основанию 16, используя строчные буквы для цифр больше 9. |
X |
Шестнадцатеричный формат. Выводит число в системе счисления по основанию 16, используя прописные буквы для цифр больше 9. |
| (None) | то же, что и 'd' |
Доступные типы представления чисел с плавающей точкой:
| Тип | Результат |
|---|---|
e |
Экспоненциальная запись. Выводит число в научной записи, используя букву 'e' для обозначения показателя степени. |
E |
Экспоненциальная запись. То же, что и 'e', за исключением преобразования числа в прописные буквы. |
f |
Запись с фиксированной точкой. Отображает число как число с фиксированной точкой. |
F |
Запись с фиксированной точкой. То же, что и 'f', за исключением преобразования числа в прописные буквы. |
g |
Общий формат. Выводит число как число с фиксированной точкой, если число слишком велико, переключается на 'e' — экспоненциальную запись. |
G |
Общий формат. То же, что и 'g', за исключением переключения на 'E' если число слишком велико. |
| (None) | аналогично 'g', за исключением того, что он выводит по крайней мере одну цифру после десятичной точки. |
Ограничения
Из-за чётко определённого порядка, в котором расширяются шаблоны и макросы, strformat не может расширить аргументы шаблона:
template myTemplate(arg: untyped): untyped =
echo "arg is: ", arg
echo &"--- {arg} ---"
let x = "abc"
myTemplate(x)
Сначала расширяется шаблон myTemplate, где каждый идентификатор arg заменяется его аргументом. arg внутри строки форматирования не виден в этом процессе, потому что это часть строкового литерала. Это ещё не идентификатор. Затем макрос strformat создаёт идентификатор arg из строкового литерала. Идентификатор, который больше нельзя разрешить.
Решение этой проблемы — привязать аргумент шаблона к новой локальной переменной.
template myTemplate(arg: untyped): untyped =
block:
let arg1 {.inject.} = arg
echo "arg is: ", arg1
echo &"--- {arg1} ---"
Использование {.inject.} здесь снова необходимо из-за порядка расширения шаблона и гигиенических шаблонов. Но поскольку мы обычно хотим сохранить гигиеничность myTemplate, и не хотим, чтобы arg1 был внедрен в контекст, где myTemplate расширяется, всё обернуто в block.
Направления развития
Выражение в фигурных скобках с запятыми, например, {x, argA, argB}, можно преобразовать в formatValue(result, x, argA, argB), чтобы поддерживать форматировщики, которым не нужно анализировать пользовательский язык внутри пользовательского языка, а вместо этого они предпочитают использовать существующий синтаксис Nim. Это также способствует удобочитаемости, поскольку в однобуквенных DSL можно запихнуть ограниченное количество информации.
Импорты
- macros, parseutils, unicode, strutils
Типы
StandardFormatSpecifier = object fill*, align*: char ## Desired fill and alignment. sign*: char ## Desired sign. alternateForm*: bool ## Whether to prefix binary, octal and hex numbers ## with ``0b``, ``0o``, ``0x``. padWithZero*: bool ## Whether to pad with zeros rather than spaces. minimumWidth*, precision*: int ## Desired minimum width and precision. typ*: char ## Type like 'f', 'g' or 'd'. endPosition*: int ## End position in the format specifier after ## ``parseStandardFormatSpecifier`` returned.- Тип, описывающий "стандартные спецификаторы формата". Исходный код Редактировать
Процедуры
proc alignString(s: string; minimumWidth: int; align = '\x00'; fill = ' '): string {...}{. raises: [], tags: [].}- Выравнивает
sс помощью символаfill. Это интересно только если вы хотите написать пользовательскуюformatпроцедуру, которая должна поддерживать стандартные спецификаторы формата. Исходный код Редактировать proc parseStandardFormatSpecifier(s: string; start = 0; ignoreUnknownSuffix = false): StandardFormatSpecifier {...}{. raises: [ValueError], tags: [].}-
Экспортируемая вспомогательная процедура, анализирующая "стандартные спецификаторы формата", как указано в грамматике:
[[fill]align][sign][#][0][minimumwidth][.precision][type]
Это интересно только если вы хотите написать пользовательскую
Исходный код Редактироватьformatпроцедуру, которая должна поддерживать стандартные спецификаторы формата. ЕслиignoreUnknownSuffixистинно, неизвестный суффикс после поляtypeне является ошибкой. proc formatValue[T: SomeInteger](result: var string; value: T; specifier: string)
- Стандартная реализация форматирования для
SomeInteger. Вызывать её напрямую бессмысленно, но она необходима для макроса&. Исходный код Редактировать proc formatValue(result: var string; value: SomeFloat; specifier: string)
- Стандартная реализация форматирования для
SomeFloat. Вызывать её напрямую бессмысленно, но она необходима для макроса&. Исходный код Редактировать proc formatValue(result: var string; value: string; specifier: string) {...}{. raises: [ValueError], tags: [].}- Стандартная реализация форматирования для
string. Вызывать её напрямую бессмысленно, но она необходима для макроса&. Исходный код Редактировать
Макросы
macro `&`(pattern: string): untyped
- Для спецификации макроса
&, см. документацию модуля. Исходный код Редактировать macro fmt(pattern: string): untyped
- Псевдоним для
&. Исходный код Редактировать macro fmt(pattern: string; openChar, closeChar: char): untyped
- Используйте
openCharвместо '{' иcloseCharвместо '}'Пример:
let testInt = 123 doAssert "<testInt>".fmt('<', '>') == "123" doAssert """(()"foo" & "bar"())""".fmt(')', '(') == "(foobar)" doAssert """ ""{"123+123"}"" """.fmt('"', '"') == " \"{246}\" "Исходный код Редактировать
© 2006–2021 Andreas Rumpf
Licensed under the MIT License.
https://nim-lang.org/docs/strformat.html