Localize.Number.Grammar (Localize v1.5.0)

Copy Markdown View Source

The grammar of a number, read from the tokens of Localize.Number.Lexer with a Localize.Number.Lexicon.t/0.

A number is read once with each of the lexicon's readings, the separator sets it may be written with, and is the value every valid reading gives. Where two readings give different values the text is refused as ambiguous rather than one guessed.

Grammar

number    = lead, body, trail ;
lead      = spaces, [ sign ], spaces, [ "(" ], spaces, [ sign ], spaces ;
trail     = spaces, [ sign ], spaces, [ ")" ], spaces, [ sign ], spaces ;
body      = integer, [ decimal, fraction ], [ exponent ] ;
integer   = digits, { group, digits } ;
fraction  = digits | zero-dash ;
zero-dash = dash, [ dash ] ;
exponent  = ( "e" | "E" ), [ sign ], digits ;

decimal and group are the marks of the reading in hand, and a space is a group where the reading groups with one. The sign and the parentheses are read from the two ends; the body between them is walked once (body_number/4), building the string Elixir parses and gathering what the rules need, which are checked at the end. The rules a number must also satisfy are the named functions below, each with the TR35 passage or decision it comes from:

  • one sign at most, before or after the digits, or parentheses, which make the number negative (signed_body/2);

  • no minus before a bare decimal mark, since "-.5" may be a zero written as a dash (dash_for_zero?/4);

  • a zero fraction written as one or two dashes, leniently only (zero_fraction/3);

  • no space inside the number unless the reading groups with one (interior_space/2);

  • no group separator in the fraction (fraction_part/4);

  • groups of the sizes the grouping mode allows (integer_grouping/3);

  • no period that is foreign to every reading, unless it is one decimal point between digits and not a thousands group (no_stray_period/3);

  • Elixir's digit separator _ only between two digits (the underscore clauses of integer_part/4 and fraction_part/4).

Summary

Functions

Reads a number from a string with a lexicon.

Reads a number from the tokens of a string, already read by Localize.Number.Lexer.tokens/2 with the same lexicon.

Types

grouping()

@type grouping() :: :lenient | :strict | :sizes

number_type()

@type number_type() :: :integer | :float | :decimal | nil

Functions

read(string, lexicon, options)

@spec read(String.t(), Localize.Number.Lexicon.t(), Keyword.t()) ::
  {:ok, number() | Decimal.t()} | {:error, Exception.t()}

Reads a number from a string with a lexicon.

Arguments

Options

  • :number is the type to read: :integer, :float, :decimal, or nil to read an integer where the text is one and a float otherwise.

  • :grouping is how group separators are checked: :lenient, :strict or :sizes.

  • :max_decimal_exponent bounds the exponent of a decimal read.

Returns

  • {:ok, number}, or

  • {:error, %Localize.InvalidValueError{}} when the text is not a number of the lexicon's locale, or

  • {:error, %Localize.ParseError{reason: :unexpected_input}} when two readings give different numbers.

Examples

iex> {:ok, de} = Localize.validate_locale(:de)
iex> {:ok, lexicon} = Localize.Number.Lexicon.fetch(de, :latn, [])
iex> Localize.Number.Grammar.read("1.234,5", lexicon, number: :decimal, grouping: :lenient)
{:ok, Decimal.new("1234.5")}

read_tokens(tokens, string, lexicon, options)

@spec read_tokens(
  [Localize.Number.Lexer.token()],
  String.t(),
  Localize.Number.Lexicon.t(),
  Keyword.t()
) ::
  {:ok, number() | Decimal.t()} | {:error, Exception.t()}

Reads a number from the tokens of a string, already read by Localize.Number.Lexer.tokens/2 with the same lexicon.

Localize.Number.Scanner reads a whole string into tokens once and hands each run with the shape of a number here, so that no run is read twice.

Arguments

  • tokens are the tokens of string.

  • string is the text the tokens were read from, which an error names.

  • lexicon is the Localize.Number.Lexicon.t/0 the tokens were read with.

  • options are the options of read/3.

Returns

  • {:ok, number}, or

  • {:error, %Localize.InvalidValueError{}} when the text is not a number of the lexicon's locale, or

  • {:error, %Localize.ParseError{reason: :unexpected_input}} when two readings give different numbers.

Examples

iex> {:ok, en} = Localize.validate_locale(:en)
iex> {:ok, lexicon} = Localize.Number.Lexicon.fetch(en, :latn, [])
iex> tokens = Localize.Number.Lexer.tokens("1,234.5", lexicon)
iex> Localize.Number.Grammar.read_tokens(tokens, "1,234.5", lexicon, [])
{:ok, 1234.5}