Packages
ex_cldr_numbers
1.1.0
2.38.3
2.38.2
2.38.1
2.38.0
2.37.0
2.36.0
2.35.2
2.35.1
2.35.0
2.34.1
2.34.0
2.33.6
2.33.5
2.33.4
2.33.3
2.33.2
2.33.1
2.33.0
2.32.4
2.32.3
2.32.2
2.32.1
2.32.0
retired
2.31.3
retired
2.31.2
2.31.1
2.31.0
2.30.1
2.30.0
2.29.0
2.28.0
2.27.3
2.27.2
2.27.1
2.27.0
2.26.0
2.25.2
2.25.1
2.25.0
2.24.0
2.23.3
2.23.2
2.23.1
2.23.0
2.23.0-rc.4
2.23.0-rc.3
2.23.0-rc.2
2.23.0-rc.1
2.23.0-rc.0
2.22.1
2.22.0
2.21.0
2.20.0
2.19.0
2.18.4
2.18.3
2.18.2
2.18.1
2.18.0
2.17.0
2.17.0-rc.1
2.17.0-rc.0
2.16.1
2.16.0
2.16.0-rc.0
2.15.4
2.15.3
2.15.2
2.15.1
2.15.0
2.14.0
2.13.2
2.13.1
2.13.0
2.13.0-rc.0
2.12.1
2.12.0
2.11.0
2.10.0
2.9.0
2.8.0
2.7.2
2.7.1
2.7.0
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.0
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.1
2.1.0
2.0.0
2.0.0-rc.0
1.6.0
1.5.2
1.5.1
1.5.0
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.1
1.3.0
1.3.0-rc.1
1.3.0-rc.0
retired
1.2.0
1.1.0
1.0.1
1.0.0
1.0.0-rc.0
retired
0.3.3
0.3.2
retired
0.3.1
retired
0.3.0
retired
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Number and currency localization and formatting functions for the Common Locale Data Repository (CLDR).
Current section
Files
Jump to
Current section
Files
lib/number/rbnf/processor.ex
# credo:disable-for-this-file
defmodule Cldr.Rbnf.Processor do
@moduledoc """
Macro to define the interpreter for the compiled RBNF rules specific to a rule group (Ordinal,
Spellout, NumberingSystem)
"""
defmacro __using__(_opts) do
quote location: :keep do
alias Cldr.Number
alias Cldr.Digits
import Cldr.Rbnf.Processor
defp do_rule(number, locale, function, rule, parsed) do
results = Enum.map(parsed, fn {operation, argument} ->
do_operation(operation, number, locale, function, rule, argument)
end)
if Enum.any?(results, fn {:error, _} -> true; _ -> false end) do
{:error, collect_errors(results)}
else
:erlang.iolist_to_binary(results)
end
end
defp collect_errors(results) do
results
|> Enum.map(fn {_, v} -> v; other -> other end)
|> Enum.join(", ")
end
defp do_operation(:literal, _number, _locale, _function, _rule, string) do
string
end
defp do_operation(:modulo, number, locale, function, rule, nil)
when is_number(number) and number < 0 do
apply(__MODULE__, function, [abs(number), locale])
end
defp do_operation(:modulo, number, locale, function, rule, {:format, format})
when is_number(number) and number < 0 do
Cldr.Number.to_string!(abs(number), locale: locale, format: format)
end
defp do_operation(:modulo, number, locale, function, rule, nil)
when is_integer(number) do
mod = number - (div(number, rule.divisor) * rule.divisor)
apply(__MODULE__, function, [mod, locale])
end
# For Fractional rules we format the integral part
defp do_operation(:modulo, number, locale, function, _rule, nil)
when is_float(number) do
format_fraction(number, locale)
end
defp do_operation(:modulo, number, locale, _function, rule, {:rule, rule_name}) do
mod = number - (div(number, rule.divisor) * rule.divisor)
apply(__MODULE__, rule_name, [mod, locale])
end
defp do_operation(:modulo, number, locale, function, rule, {:format, format}) do
mod = number - (div(number, rule.divisor) * rule.divisor)
Cldr.Number.to_string!(mod, locale: locale, format: format)
end
# For Fractional rules we format the fraction as individual digits.
defp do_operation(:quotient, number, locale, function, rule, nil)
when is_float(number) do
apply(__MODULE__, function, [trunc(number), locale])
end
defp do_operation(:quotient, number, locale, function, rule, nil) do
divisor = div(number, rule.divisor)
apply(__MODULE__, function, [divisor, locale])
end
defp do_operation(:quotient, number, locale, _function, rule, {:rule, rule_name}) do
divisor = div(number, rule.divisor)
apply(__MODULE__, rule_name, [divisor, locale])
end
defp do_operation(:call, number, locale, _function, _rule, {:format, format}) do
Cldr.Number.to_string!(number, locale: locale, format: format)
end
defp do_operation(:call, number, locale, _function, _rule, {:rule, rule_name}) do
apply(__MODULE__, rule_name, [number, locale])
end
defp do_operation(:ordinal, number, locale, _function, _rule, plurals) do
plural = Cldr.Number.Ordinal.plural_rule(number, locale)
Map.get(plurals, plural) || Map.get(plurals, :other)
end
defp do_operation(:cardinal, number, locale, _function, _rule, plurals) do
plural = Cldr.Number.Cardinal.plural_rule(number, locale)
Map.get(plurals, plural) || Map.get(plurals, :other)
end
defp do_operation(:conditional, number, locale, function, rule, argument) do
mod = number - (div(number, rule.divisor) * rule.divisor)
if mod > 0 do
do_rule(mod, locale, function, rule, argument)
else
""
end
end
defp format_fraction(number, locale) do
fraction = number
|> Digits.fraction_as_integer
|> Integer.to_string
|> String.split("", trim: true)
|> Enum.map(&String.to_integer/1)
|> Enum.map(&Cldr.Rbnf.Spellout.spellout_cardinal(&1, locale))
|> Enum.join(" ")
end
@before_compile Cldr.Rbnf.Processor
end
end
@public_rulesets :public_rulesets
def define_rules(rule_group_name, env) do
Module.register_attribute(env.module, @public_rulesets, [])
iterate_rules rule_group_name, fn
(rule_group, locale, "public", :error) ->
define_rule(:error, nil, rule_group, locale, nil)
|> Code.eval_quoted([], env)
(_rule_group, _locale, "private", :error) ->
nil
(rule_group, locale, access, rule) ->
{:ok, parsed} = Cldr.Rbnf.Rule.parse(rule.definition)
function_body = rule_body(locale, rule_group, rule, parsed)
rule.base_value
|> define_rule(rule.range, rule_group, locale, function_body)
|> add_function_to_exports(access, env.module, locale)
|> Code.eval_quoted([], env)
end
end
defp iterate_rules(rule_group_type, fun) do
all_rules = Cldr.Rbnf.for_all_locales[rule_group_type]
unless is_nil(all_rules) do
for {locale_name, _rule_group} <- all_rules do
for {rule_group, %{access: access, rules: rules}} <- all_rules[locale_name] do
for rule <- rules do
fun.(rule_group, locale_name, access, rule)
end
fun.(rule_group, locale_name, access, :error)
end
end
end
end
# If we are provided with a Decimal integer then we can call the
# equivalent integer function without loss of precision
defp define_rule(:error, _range, rule_group, locale_name, _body) do
quote do
def unquote(rule_group)(%Decimal{exp: 0, coef: number},
%Cldr.LanguageTag{rbnf_locale_name: unquote(locale_name)} = locale) do
unquote(rule_group)(number, locale)
end
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: unquote(locale_name)}) do
{:error, rbnf_rule_error(number, unquote(rule_group), unquote(locale_name))}
end
end
end
defp define_rule("-x", _range, rule_group, locale, body) do
quote do
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: unquote(locale)})
when Kernel.and(is_number(number), number < 0), do: unquote(body)
end
end
# Improper fraction rule
defp define_rule("x.x", _range, rule_group, locale, body) do
quote do
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: unquote(locale)})
when is_float(number), do: unquote(body)
end
end
defp define_rule("x,x", range, rule_group, locale, body) do
define_rule("x.x", range, rule_group, locale, body)
end
defp define_rule(0, "undefined", rule_group, locale, body) do
quote do
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: unquote(locale)})
when is_integer(number), do: unquote(body)
end
end
defp define_rule(base_value, "undefined", rule_group, locale, body)
when is_integer(base_value) do
quote do
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: unquote(locale)})
when Kernel.and(is_integer(number), number >= unquote(base_value)),
do: unquote(body)
end
end
defp define_rule(base_value, range, rule_group, locale, body)
when is_integer(range) and is_integer(base_value) do
quote do
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: unquote(locale)})
when Kernel.and(is_integer(number),
Kernel.and(number >= unquote(base_value), number < unquote(range))),
do: unquote(body)
end
end
defp define_rule(base_value, "undefined", rule_group, locale, body)
when is_integer(base_value) do
quote do
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: unquote(locale)})
when Kernel.and(is_integer(number), number >= unquote(base_value)),
do: unquote(body)
end
end
defp define_rule("Inf", _range, _rule_group, _locale, _body) do
{:error, "Infinite rule sets are not implemented"}
end
defp define_rule("NaN", _range, _rule_group, _locale, _body) do
{:error, "NaN rule sets are not implemented"}
end
defp define_rule("0.x", _range, _rule_group, _locale, _body) do
{:error, "Proper Fraction rule sets are not implemented"}
end
defp define_rule("x.0", _range, _rule_group, _locale, _body) do
{:error, "Master rule sets are not implemented"}
end
# Get the AST of the rule body
defp rule_body(locale, rule_group, rule, parsed) do
quote do
do_rule(number,
unquote(Macro.escape(Cldr.Locale.new!(locale))),
unquote(rule_group),
unquote(Macro.escape(rule)),
unquote(Macro.escape(parsed)))
end
end
# Keep track of the public rulesets per locale so we can introspect the
# public interface
defp add_function_to_exports({:def, _aliases, [{:when, _, [{name, _, _} | _]} | _]} = function,
"public", module, locale) do
public_rulesets = Module.get_attribute(module, @public_rulesets) || %{}
locale_public_rulesets = [name | (Map.get(public_rulesets, locale) || [])]
Module.put_attribute module, @public_rulesets,
Map.put(public_rulesets, locale, Enum.uniq(locale_public_rulesets))
function
end
defp add_function_to_exports(function, _access, _module, _locale) do
function
end
def rbnf_rule_error(number, rule_group, locale_name) do
{Cldr.Rbnf.NoRuleForNumber,
"rule group #{inspect rule_group} for locale #{inspect locale_name} does not " <>
"know how to process #{inspect number}"}
end
defmacro __before_compile__(env) do
quote do
def rule_sets(%Cldr.LanguageTag{rbnf_locale_name: rbnf_locale_name}) do
unquote(Macro.escape(Module.get_attribute(env.module, :public_rulesets)))
|> Map.get(rbnf_locale_name)
end
end
end
end