# `Localize.Number.Grammar`
[🔗](https://github.com/elixir-localize/localize/blob/v1.5.0/lib/localize/number/grammar.ex#L1)

The grammar of a number, read from the tokens of
`Localize.Number.Lexer` with a `t: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

```ebnf
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`).

# `grouping`

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

# `number_type`

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

# `read`

```elixir
@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

* `string` is the text to read.

* `lexicon` is the `t:Localize.Number.Lexicon.t/0` to read it with.

* `options` is a keyword list of options.

### 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`

```elixir
@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 `t: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}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
