Packages
ex_cldr_numbers
2.35.2
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/cldr/number/rbnf/processor.ex
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
backend = opts[:backend]
ordinal_module = Module.concat(backend, Number.Ordinal)
cardinal_module = Module.concat(backend, Number.Cardinal)
spellout_module = Module.concat(backend, Rbnf.Spellout)
quote location: :keep do
alias Cldr.Number
alias Cldr.Digits
import Cldr.Rbnf.Processor
Module.put_attribute(__MODULE__, :backend, unquote(backend))
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
# When a rule is specified in a -x rule
defp do_operation(:modulo, number, locale, _function, rule, {:rule, rule_name})
when is_number(number) and number < 0 do
apply(__MODULE__, rule_name, [abs(number), locale])
end
# When a rule is not specifieid in a -x rule
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), unquote(backend), 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, unquote(backend), 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, unquote(backend), 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 = unquote(ordinal_module).plural_rule(number, locale)
Map.get(plurals, plural) || Map.get(plurals, :other)
end
defp do_operation(:cardinal, number, locale, _function, _rule, plurals) do
plural = unquote(cardinal_module).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(&unquote(spellout_module).spellout_numbering(&1, locale))
|> Enum.join(" ")
end
@before_compile Cldr.Rbnf.Processor
end
end
@public_rulesets :public_rulesets
def define_rules(rule_group_name, backend, env) do
Module.register_attribute(env.module, @public_rulesets, [])
enumerate_rules(rule_group_name, backend, 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, "public", :redirect ->
define_rule(:redirect, backend, rule_group, locale, nil)
|> Code.eval_quoted([], env)
_rule_group, _locale, "private", :redirect ->
nil
rule_group, locale, access, rule ->
function_name = Cldr.Rbnf.force_valid_function_name(rule_group)
{:ok, parsed} = Cldr.Rbnf.Rule.parse(rule.definition)
function_body = rule_body(locale, rule_group, rule, parsed, backend)
rule.base_value
|> define_rule(rule.range, function_name, locale, function_body)
|> add_function_to_exports(access, env.module, locale)
|> Code.eval_quoted([], env)
end)
end
defp enumerate_rules(rule_group_type, backend, fun) do
all_rules = Cldr.Rbnf.for_all_locales(backend)[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, :redirect)
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 location: :keep 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(:redirect, backend, rule_group, locale_name, _body) do
quote location: :keep do
def unquote(rule_group)(number, unquote(locale_name)) do
with {:ok, locale} <- Module.concat(unquote(backend), Locale).new(unquote(locale_name)) do
unquote(rule_group)(number, locale)
end
end
end
end
defp define_rule("-x", _range, rule_group, locale_name, body) do
quote location: :keep do
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: unquote(locale_name)})
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_name, body) do
quote location: :keep do
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: unquote(locale_name)})
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_name, body) do
quote location: :keep do
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: unquote(locale_name)})
when is_integer(number),
do: unquote(body)
end
end
defp define_rule(base_value, "undefined", rule_group, locale_name, body)
when is_integer(base_value) do
quote location: :keep do
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: unquote(locale_name)})
when Kernel.and(is_integer(number), number >= unquote(base_value)),
do: unquote(body)
end
end
defp define_rule(base_value, range, rule_group, locale_name, body)
when is_integer(range) and is_integer(base_value) do
quote location: :keep do
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: unquote(locale_name)})
when Kernel.and(
is_integer(number),
Kernel.and(number >= unquote(base_value), number < unquote(range))
),
do: unquote(body)
end
end
defp define_rule("Inf", _range, _rule_group, _locale_name, _body) do
{:error, "Infinite rule sets are not implemented"}
end
defp define_rule("NaN", _range, _rule_group, _locale_name, _body) do
{:error, "NaN rule sets are not implemented"}
end
defp define_rule("0.x", _range, _rule_group, _locale_name, _body) do
{:error, "Proper Fraction rule sets are not implemented"}
end
defp define_rule("x.0", _range, _rule_group, _locale_name, _body) do
{:error, "Master rule sets are not implemented"}
end
# Get the AST of the rule body
defp rule_body(locale_name, rule_group, rule, parsed, _backend) do
locale =
Cldr.Config.all_language_tags()
|> Map.get(locale_name)
quote location: :keep do
do_rule(
number,
unquote(Macro.escape(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(other, _access, _module, _locale) do
other
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
module = env.module
backend =
module
|> Module.get_attribute(:backend)
rule_sets =
module
|> Module.get_attribute(:public_rulesets)
all_rule_sets =
rule_sets
|> Map.values()
|> Elixir.List.flatten()
|> Enum.uniq()
|> Enum.sort()
rule_sets = Macro.escape(rule_sets)
quote location: :keep,
bind_quoted: [
rule_sets: rule_sets,
all_rule_sets: all_rule_sets,
backend: backend,
module: module
] do
# A map of rule sets by locale
def rule_sets do
unquote(Macro.escape(rule_sets))
end
# All rule sets for a locale
def rule_sets(%Cldr.LanguageTag{rbnf_locale_name: rbnf_locale_name}) do
rule_sets(rbnf_locale_name)
end
def rule_sets(rbnf_locale_name) when is_atom(rbnf_locale_name) do
Map.get(rule_sets(), rbnf_locale_name)
end
def rule_sets(rbnf_locale_name) when is_binary(rbnf_locale_name) do
rbnf_locale_name
|> String.to_existing_atom()
|> rule_sets
rescue
ArgumentError ->
nil
end
# All rule sets for all locales
def all_rule_sets do
unquote(all_rule_sets)
end
# Return an error for a valid rule set which
# is not supported for either the locale or
# the number
for rule_group <- all_rule_sets do
@dialyzer {:nowarn_function, [{rule_group, 2}]}
def unquote(rule_group)(number, locale_name)
when is_atom(locale_name) or is_binary(locale_name) do
with {:ok, locale} <- unquote(backend).validate_locale(locale_name) do
unquote(rule_group)(number, locale)
end
end
def unquote(rule_group)(number, %Cldr.LanguageTag{rbnf_locale_name: rbnf_locale_name}) do
{:error, rbnf_rule_error(number, unquote(rule_group), rbnf_locale_name)}
end
# NumberSystem rules are only in the root locale so
# lets make it easier to use them by defaulting the locale
if hd(Enum.reverse(Module.split(module))) == "NumberSystem" do
def unquote(rule_group)(number) do
unquote(rule_group)(number, unquote(Cldr.Config.root_locale_name()))
end
end
end
end
end
end