defmodule Money.Combinators do @moduledoc false import NimbleParsec # Whitespace as defined by Unicode set :Zs plus tab # non-breaking-space is considered a separator, not whitespace @whitespace [?\s, ?\t, 0x1680, 0x2000, 0x202F, 0x205F, 0x3000] def whitespace do repeat(empty(), utf8_char(@whitespace)) |> label("whitespace") end @separators [?., ?,, ?،, ?٫, ?、, ?︐, ?︑, ?﹐, ?﹑, ?,, ?、, ? , 0x00A0] def separators do utf8_char(@separators) |> label("separators") end @decimal_places [?., ?․, ?。, ?︒, ?﹒, ?., ?。] def decimal_place do utf8_char(@decimal_places) |> label("decimal place character") end @left_parens [?(] def left_paren do utf8_char(@left_parens) |> label("left parenthesis") end @right_parens [?)] def right_paren do utf8_char(@right_parens) |> label("right parenthesis") end @digits [?0..?9] def digits do repeat(empty(), ascii_char([?0..?9])) |> label("digits") end @minus [?-] def minus do ascii_char(@minus) end def positive_number do digits() |> repeat(separators() |> concat(digits())) |> optional(decimal_place() |> concat(digits())) |> label("positive number") end def negative_number do choice(empty(), [ ignore(minus()) |> concat(positive_number()), positive_number() |> ignore(minus()) ]) |> reduce({List, :to_string, []}) |> map(:add_minus_sign) |> label("negative number") end def number do choice(empty(), [negative_number(), positive_number()]) |> reduce({List, :to_string, []}) |> unwrap_and_tag(:amount) |> label("number") end @rtl [0x200F] def rtl do ignore(utf8_char(@rtl)) end @invalid_chars @digits ++ @left_parens ++ @minus ++ @rtl @currency Enum.map(@invalid_chars, fn s -> {:not, s} end) def currency do utf8_char(@currency) |> times(min: 1) |> reduce({List, :to_string, []}) |> unwrap_and_tag(:currency) |> label("currency code, symbol or name") end def money_with_currency do choice(empty(), [ optional(rtl()) |> concat(number()) |> ignore(optional(whitespace())) |> optional(currency()) |> optional(rtl()) |> eos(), optional(rtl()) |> optional(currency()) |> ignore(optional(whitespace())) |> concat(number()) |> optional(rtl()) |> eos() ]) |> label("money with currency") end def accounting_format do choice(empty(), [ ignore(left_paren()) |> ignore(optional(whitespace())) |> concat(number()) |> ignore(optional(whitespace())) |> optional(currency()) |> ignore(optional(whitespace())) |> ignore(right_paren()), ignore(left_paren()) |> ignore(optional(whitespace())) |> optional(currency()) |> ignore(optional(whitespace())) |> concat(number()) |> ignore(optional(whitespace())) |> ignore(right_paren()) ]) |> map(:change_sign) |> label("money with currency in accounting format") end end