aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/src/erl_anno.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib/src/erl_anno.erl')
-rw-r--r--lib/stdlib/src/erl_anno.erl405
1 files changed, 405 insertions, 0 deletions
diff --git a/lib/stdlib/src/erl_anno.erl b/lib/stdlib/src/erl_anno.erl
new file mode 100644
index 0000000000..d32c34dabd
--- /dev/null
+++ b/lib/stdlib/src/erl_anno.erl
@@ -0,0 +1,405 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2015. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(erl_anno).
+
+-export([new/1, is_anno/1]).
+-export([column/1, end_location/1, file/1, generated/1,
+ line/1, location/1, record/1, text/1]).
+-export([set_file/2, set_generated/2, set_line/2, set_location/2,
+ set_record/2, set_text/2]).
+
+%% To be used when necessary to avoid Dialyzer warnings.
+-export([to_term/1, from_term/1]).
+
+-export_type([anno/0, line/0, column/0, location/0, text/0]).
+
+-export_type([anno_term/0]).
+
+-define(LN(L), is_integer(L), L >= 0).
+-define(COL(C), (is_integer(C) andalso C >= 1)).
+
+%% Location.
+-define(LCOLUMN(C), ?COL(C)).
+-define(LLINE(L), ?LN(L)).
+
+%% Debug: define DEBUG to make sure that annotations are handled as an
+%% opaque type. Note that all abstract code need to be compiled with
+%% DEBUG=true. See also ./erl_pp.erl.
+
+%-define(DEBUG, true).
+
+-type annotation() :: {'file', filename()}
+ | {'generated', generated()}
+ | {'location', location()}
+ | {'record', record()}
+ | {'text', string()}.
+
+-opaque anno() :: location() | [annotation(), ...].
+-type anno_term() :: term().
+
+-type column() :: pos_integer().
+-type generated() :: boolean().
+-type filename() :: file:filename_all().
+-type line() :: non_neg_integer().
+-type location() :: line() | {line(), column()}.
+-type record() :: boolean().
+-type text() :: string().
+
+-ifdef(DEBUG).
+%% Anything 'false' accepted by the compiler.
+-define(ALINE(A), is_reference(A)).
+-define(ACOLUMN(A), is_reference(A)).
+-else.
+-define(ALINE(L), ?LN(L)).
+-define(ACOLUMN(C), ?COL(C)).
+-endif.
+
+-spec to_term(Anno) -> anno_term() when
+ Anno :: anno().
+
+-ifdef(DEBUG).
+to_term(Anno) ->
+ simplify(Anno).
+-else.
+to_term(Anno) ->
+ Anno.
+-endif.
+
+-spec from_term(Term) -> Anno when
+ Term :: anno_term(),
+ Anno :: anno().
+
+-ifdef(DEBUG).
+from_term(Term) when is_list(Term) ->
+ Term;
+from_term(Line) when is_integer(Line), Line < 0 -> % Before OTP 19
+ set_generated(true, new(-Line));
+from_term(Term) ->
+ [{location, Term}].
+-else.
+from_term(Line) when is_integer(Line), Line < 0 -> % Before OTP 19
+ set_generated(true, new(-Line));
+from_term(Term) ->
+ Term.
+-endif.
+
+-spec new(Location) -> anno() when
+ Location :: location().
+
+new(Line) when ?LLINE(Line) ->
+ new_location(Line);
+new({Line, Column}=Loc) when ?LLINE(Line), ?LCOLUMN(Column) ->
+ new_location(Loc);
+new(Term) ->
+ erlang:error(badarg, [Term]).
+
+-ifdef(DEBUG).
+new_location(Location) ->
+ [{location, Location}].
+-else.
+new_location(Location) ->
+ Location.
+-endif.
+
+-spec is_anno(Term) -> boolean() when
+ Term :: any().
+
+is_anno(Line) when ?ALINE(Line) ->
+ true;
+is_anno({Line, Column}) when ?ALINE(Line), ?ACOLUMN(Column) ->
+ true;
+is_anno(Anno) ->
+ (Anno =/= [] andalso
+ is_anno1(Anno) andalso
+ lists:keymember(location, 1, Anno)).
+
+is_anno1([{Item, Value}|Anno]) ->
+ is_anno2(Item, Value) andalso is_anno1(Anno);
+is_anno1(A) ->
+ A =:= [].
+
+is_anno2(location, Line) when ?LN(Line) ->
+ true;
+is_anno2(location, {Line, Column}) when ?LN(Line), ?COL(Column) ->
+ true;
+is_anno2(generated, true) ->
+ true;
+is_anno2(file, Filename) ->
+ is_filename(Filename);
+is_anno2(record, true) ->
+ true;
+is_anno2(text, Text) ->
+ is_string(Text);
+is_anno2(_, _) ->
+ false.
+
+is_filename(T) ->
+ is_list(T) orelse is_binary(T).
+
+is_string(T) ->
+ is_list(T).
+
+-spec column(Anno) -> column() | 'undefined' when
+ Anno :: anno().
+
+column({Line, Column}) when ?ALINE(Line), ?ACOLUMN(Column) ->
+ Column;
+column(Line) when ?ALINE(Line) ->
+ undefined;
+column(Anno) ->
+ case location(Anno) of
+ {_Line, Column} ->
+ Column;
+ _Line ->
+ undefined
+ end.
+
+-spec end_location(Anno) -> location() | 'undefined' when
+ Anno :: anno().
+
+end_location(Anno) ->
+ case text(Anno) of
+ undefined ->
+ undefined;
+ Text ->
+ case location(Anno) of
+ {Line, Column} ->
+ end_location(Text, Line, Column);
+ Line ->
+ end_location(Text, Line)
+ end
+ end.
+
+-spec file(Anno) -> filename() | 'undefined' when
+ Anno :: anno().
+
+file(Line) when ?ALINE(Line) ->
+ undefined;
+file({Line, Column}) when ?ALINE(Line), ?ACOLUMN(Column) ->
+ undefined;
+file(Anno) ->
+ anno_info(Anno, file).
+
+-spec generated(Anno) -> generated() when
+ Anno :: anno().
+
+generated(Line) when ?ALINE(Line) ->
+ false;
+generated({Line, Column}) when ?ALINE(Line), ?ACOLUMN(Column) ->
+ false;
+generated(Anno) ->
+ anno_info(Anno, generated, false).
+
+-spec line(Anno) -> line() when
+ Anno :: anno().
+
+line(Anno) ->
+ case location(Anno) of
+ {Line, _Column} ->
+ Line;
+ Line ->
+ Line
+ end.
+
+-spec location(Anno) -> location() when
+ Anno :: anno().
+
+location(Line) when ?ALINE(Line) ->
+ Line;
+location({Line, Column}=Location) when ?ALINE(Line), ?ACOLUMN(Column) ->
+ Location;
+location(Anno) ->
+ anno_info(Anno, location).
+
+-spec record(Anno) -> record() when
+ Anno :: anno().
+
+record(Line) when ?ALINE(Line) ->
+ false;
+record({Line, Column}) when ?ALINE(Line), ?ACOLUMN(Column) ->
+ false;
+record(Anno) ->
+ anno_info(Anno, record, false).
+
+-spec text(Anno) -> text() | 'undefined' when
+ Anno :: anno().
+
+text(Line) when ?ALINE(Line) ->
+ undefined;
+text({Line, Column}) when ?ALINE(Line), ?ACOLUMN(Column) ->
+ undefined;
+text(Anno) ->
+ anno_info(Anno, text).
+
+-spec set_file(File, Anno) -> Anno when
+ File :: filename(),
+ Anno :: anno().
+
+set_file(File, Anno) ->
+ set(file, File, Anno).
+
+-spec set_generated(Generated, Anno) -> Anno when
+ Generated :: generated(),
+ Anno :: anno().
+
+set_generated(Generated, Anno) ->
+ set(generated, Generated, Anno).
+
+-spec set_line(Line, Anno) -> Anno when
+ Line :: line(),
+ Anno :: anno().
+
+set_line(Line, Anno) ->
+ case location(Anno) of
+ {_Line, Column} ->
+ set_location({Line, Column}, Anno);
+ _Line ->
+ set_location(Line, Anno)
+ end.
+
+-spec set_location(Location, Anno) -> Anno when
+ Location :: location(),
+ Anno :: anno().
+
+set_location(Line, L) when ?ALINE(L), ?LLINE(Line) ->
+ new_location(Line);
+set_location(Line, {L, Column}) when ?ALINE(L), ?ACOLUMN(Column),
+ ?LLINE(Line) ->
+ new_location(Line);
+set_location({L, C}=Loc, Line) when ?ALINE(Line), ?LLINE(L), ?LCOLUMN(C) ->
+ new_location(Loc);
+set_location({L, C}=Loc, {Line, Column}) when ?ALINE(Line), ?ACOLUMN(Column),
+ ?LLINE(L), ?LCOLUMN(C) ->
+ new_location(Loc);
+set_location(Location, Anno) ->
+ set(location, Location, Anno).
+
+-spec set_record(Record, Anno) -> Anno when
+ Record :: record(),
+ Anno :: anno().
+
+set_record(Record, Anno) ->
+ set(record, Record, Anno).
+
+-spec set_text(Text, Anno) -> Anno when
+ Text :: text(),
+ Anno :: anno().
+
+set_text(Text, Anno) ->
+ set(text, Text, Anno).
+
+set(Item, Value, Anno) ->
+ case {is_settable(Item, Value), Anno} of
+ {true, Line} when ?ALINE(Line) ->
+ set_anno(Item, Value, [{location, Line}]);
+ {true, {L, C}=Location} when ?ALINE(L), ?ACOLUMN(C) ->
+ set_anno(Item, Value, [{location, Location}]);
+ {true, A} when is_list(A), A =/= [] ->
+ set_anno(Item, Value, Anno);
+ _ ->
+ erlang:error(badarg, [Item, Value, Anno])
+ end.
+
+set_anno(Item, Value, Anno) ->
+ case default(Item, Value) of
+ true ->
+ reset(Anno, Item);
+ false ->
+ R = case anno_info(Anno, Item) of
+ undefined ->
+ [{Item, Value}|Anno];
+ _ ->
+ lists:keyreplace(Item, 1, Anno, {Item, Value})
+ end,
+ reset_simplify(R)
+ end.
+
+reset(Anno, Item) ->
+ A = lists:keydelete(Item, 1, Anno),
+ reset_simplify(A).
+
+-ifdef(DEBUG).
+reset_simplify(A) ->
+ A.
+-else.
+reset_simplify(A) ->
+ simplify(A).
+-endif.
+
+simplify([{location, Location}]) ->
+ Location;
+simplify(Anno) ->
+ Anno.
+
+anno_info(Anno, Item, Default) ->
+ try lists:keyfind(Item, 1, Anno) of
+ false ->
+ Default;
+ {Item, Value} ->
+ Value
+ catch
+ _:_ ->
+ erlang:error(badarg, [Anno])
+ end.
+
+anno_info(Anno, Item) ->
+ try lists:keyfind(Item, 1, Anno) of
+ {Item, Value} ->
+ Value;
+ false ->
+ undefined
+ catch
+ _:_ ->
+ erlang:error(badarg, [Anno])
+ end.
+
+end_location("", Line, Column) ->
+ {Line, Column};
+end_location([$\n|String], Line, _Column) ->
+ end_location(String, Line+1, 1);
+end_location([_|String], Line, Column) ->
+ end_location(String, Line, Column+1).
+
+end_location("", Line) ->
+ Line;
+end_location([$\n|String], Line) ->
+ end_location(String, Line+1);
+end_location([_|String], Line) ->
+ end_location(String, Line).
+
+is_settable(file, File) ->
+ is_filename(File);
+is_settable(generated, Boolean) when Boolean; not Boolean ->
+ true;
+is_settable(location, Line) when ?LLINE(Line) ->
+ true;
+is_settable(location, {Line, Column}) when ?LLINE(Line), ?LCOLUMN(Column) ->
+ true;
+is_settable(record, Boolean) when Boolean; not Boolean ->
+ true;
+is_settable(text, Text) ->
+ is_string(Text);
+is_settable(_, _) ->
+ false.
+
+default(generated, false) -> true;
+default(record, false) -> true;
+default(_, _) -> false.