1%-
2% Copyright (c) 2012-2014 Yakaz
3% Copyright (c) 2016-2018 Jean-Sébastien Pédron <jean-sebastien.pedron@dumbbell.fr>
4% All rights reserved.
5%
6% Redistribution and use in source and binary forms, with or without
7% modification, are permitted provided that the following conditions
8% are met:
9% 1. Redistributions of source code must retain the above copyright
10%    notice, this list of conditions and the following disclaimer.
11% 2. Redistributions in binary form must reproduce the above copyright
12%    notice, this list of conditions and the following disclaimer in the
13%    documentation and/or other materials provided with the distribution.
14%
15% THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16% ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18% ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19% FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20% DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21% OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24% OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25% SUCH DAMAGE.
26
27%% @private
28
29-module(yamerl_node_int_ext).
30
31-include("yamerl_errors.hrl").
32-include("yamerl_tokens.hrl").
33-include("yamerl_nodes.hrl").
34-include("internal/yamerl_constr.hrl").
35
36%% Public API.
37-export([
38    tags/0,
39    try_construct_token/3,
40    construct_token/3,
41    node_pres/1,
42    string_to_integer/1
43  ]).
44
45%% Internal use only.
46-export([
47    base2_to_integer/2,
48    base8_to_integer/2,
49    base10_to_integer/2,
50    base16_to_integer/2,
51    base60_to_integer/3
52  ]).
53
54-define(TAG, "tag:yaml.org,2002:int").
55
56%% -------------------------------------------------------------------
57%% Public API.
58%% -------------------------------------------------------------------
59
60tags() -> [?TAG].
61
62try_construct_token(Constr, Node,
63  #yamerl_scalar{tag = #yamerl_tag{uri = {non_specific, "?"}}} = Token) ->
64    try
65        construct_token(Constr, Node, Token)
66    catch
67        _:#yamerl_parsing_error{name = not_an_integer} ->
68            unrecognized
69    end;
70try_construct_token(_, _, _) ->
71    unrecognized.
72
73construct_token(#yamerl_constr{detailed_constr = false},
74  undefined, #yamerl_scalar{text = Text} = Token) ->
75    case string_to_integer(Text) of
76        error ->
77            exception(Token);
78        Int ->
79            {finished, Int}
80    end;
81construct_token(#yamerl_constr{detailed_constr = true},
82  undefined, #yamerl_scalar{text = Text} = Token) ->
83    case string_to_integer(Text) of
84        error ->
85            exception(Token);
86        Int ->
87            Pres = yamerl_constr:get_pres_details(Token),
88            Node = #yamerl_int{
89              module = ?MODULE,
90              tag    = ?TAG,
91              pres   = Pres,
92              value  = Int
93            },
94            {finished, Node}
95    end;
96
97construct_token(_, _, Token) ->
98    exception(Token).
99
100node_pres(Node) ->
101    ?NODE_PRES(Node).
102
103%% -------------------------------------------------------------------
104%% Internal functions.
105%% -------------------------------------------------------------------
106
107%% Sign.
108string_to_integer([$+ | Text]) ->
109    string_to_integer2(Text);
110string_to_integer([$- | Text]) ->
111    case string_to_integer2(Text) of
112        error -> error;
113        Int   -> -Int
114    end;
115string_to_integer(Text) ->
116    string_to_integer2(Text).
117
118%% Base.
119string_to_integer2("0b" ++ Text) ->
120    base2_to_integer(Text, 0);
121string_to_integer2("0x" ++ Text) ->
122    base16_to_integer(Text, 0);
123string_to_integer2("0" ++ Text) ->
124    base8_to_integer(Text, 0);
125string_to_integer2(Text) ->
126    Opts = [{capture, none}, unicode],
127    case re:run(Text, "^[1-9][0-9_]*(:[0-5]?[0-9])+$", Opts) of
128        match   -> base60_to_integer(Text, 0, 0);
129        nomatch -> base10_to_integer(Text, 0)
130    end.
131
132%% Parsing.
133base10_to_integer([C | Rest], Int) when C >= $0 andalso C =< $9 ->
134    Int1 = (Int * 10) + (C - $0),
135    base10_to_integer(Rest, Int1);
136base10_to_integer([$_ | Rest], Int) ->
137    base10_to_integer(Rest, Int);
138base10_to_integer([], Int) ->
139    Int;
140base10_to_integer(_, _) ->
141    error.
142
143base2_to_integer([C | Rest], Int) when C == $0 orelse C == $1 ->
144    Int1 = (Int * 2) + (C - $0),
145    base2_to_integer(Rest, Int1);
146base2_to_integer([$_ | Rest], Int) ->
147    base2_to_integer(Rest, Int);
148base2_to_integer([], Int) ->
149    Int;
150base2_to_integer(_, _) ->
151    error.
152
153base8_to_integer([C | Rest], Int) when C >= $0 andalso C =< $7 ->
154    Int1 = (Int * 8) + (C - $0),
155    base8_to_integer(Rest, Int1);
156base8_to_integer([$_ | Rest], Int) ->
157    base8_to_integer(Rest, Int);
158base8_to_integer([], Int) ->
159    Int;
160base8_to_integer(_, _) ->
161    error.
162
163base16_to_integer([C | Rest], Int) when C >= $0 andalso C =< $9 ->
164    Int1 = (Int * 16) + (C - $0),
165    base16_to_integer(Rest, Int1);
166base16_to_integer([C | Rest], Int) when C >= $a andalso C =< $f ->
167    Int1 = (Int * 16) + (C - $a + 10),
168    base16_to_integer(Rest, Int1);
169base16_to_integer([C | Rest], Int) when C >= $A andalso C =< $F ->
170    Int1 = (Int * 16) + (C - $A + 10),
171    base16_to_integer(Rest, Int1);
172base16_to_integer([$_ | Rest], Int) ->
173    base16_to_integer(Rest, Int);
174base16_to_integer([], Int) ->
175    Int;
176base16_to_integer(_, _) ->
177    error.
178
179base60_to_integer([C | Rest], Current, Int) when C >= $0 andalso C =< $9 ->
180    Current1 = (Current * 10) + (C - $0),
181    base60_to_integer(Rest, Current1, Int);
182base60_to_integer([$: | Rest], Current, Int) ->
183    Int1 = (Int * 60) + Current,
184    base60_to_integer(Rest, 0, Int1);
185base60_to_integer([$_ | Rest], Current, Int) ->
186    base60_to_integer(Rest, Current, Int);
187base60_to_integer([], Current, Int) ->
188    (Int * 60) + Current;
189base60_to_integer(_, _, _) ->
190    error.
191
192exception(Token) ->
193    Error = #yamerl_parsing_error{
194      name   = not_an_integer,
195      token  = Token,
196      text   = "Invalid integer",
197      line   = ?TOKEN_LINE(Token),
198      column = ?TOKEN_COLUMN(Token)
199    },
200    throw(Error).
201