
* Add compiler warnings Add include_type annotation to position Add warning for unused includes Add warning for unused stateful annotation Add warning for unused functions Add warning for shadowed variables Add division by zero warning Add warning for negative spends Add warning for unused variables Add warning for unused parameters Change the ets table type to set for unused vars Add warning for unused type defs Move unused variables warning to the top level Temporarily disable unused functions warnings Add all kinds of warnings to a single ets table Enable warnings separately through options Use when_option instead of enabled_warnings Turn warnings into type errors with warn_error option Enable warning package warn_all Re-enable unused functions warnings Report warnings as type errors in a separate function Make unused_function a recognized warning Report warnings as a result of compilation Fix tests and error for unknown warnings options Fix dialyzer warnings Do not show warning for variables called "_" Move warnings handling into a separate module Do not show warning for unused public functions in namespaces Add src file name to unused include warning Mark public functions in namespaces as used Add tests for added warnings Add warning for unused return value Add test for turning warnings into type errors * Update CHANGELOG
760 lines
28 KiB
Erlang
760 lines
28 KiB
Erlang
%%% File : aeso_parser.erl
|
|
%%% Author : Ulf Norell
|
|
%%% Description :
|
|
%%% Created : 1 Mar 2018 by Ulf Norell
|
|
-module(aeso_parser).
|
|
-compile({no_auto_import,[map_get/2]}).
|
|
|
|
-export([string/1,
|
|
string/2,
|
|
string/3,
|
|
auto_imports/1,
|
|
hash_include/2,
|
|
decl/0,
|
|
type/0,
|
|
body/0,
|
|
maybe_block/1,
|
|
run_parser/2,
|
|
run_parser/3]).
|
|
|
|
-include("aeso_parse_lib.hrl").
|
|
-import(aeso_parse_lib, [current_file/0, set_current_file/1,
|
|
current_include_type/0, set_current_include_type/1]).
|
|
|
|
-type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none().
|
|
|
|
-type include_hash() :: {string(), binary()}.
|
|
|
|
|
|
escape_errors({ok, Ok}) ->
|
|
Ok;
|
|
escape_errors({error, Err}) ->
|
|
parse_error(Err).
|
|
|
|
-spec string(string()) -> parse_result().
|
|
string(String) ->
|
|
string(String, sets:new(), []).
|
|
|
|
-spec string(string(), aeso_compiler:options()) -> parse_result().
|
|
string(String, Opts) ->
|
|
case lists:keyfind(src_file, 1, Opts) of
|
|
{src_file, File} -> string(String, sets:add_element(File, sets:new()), Opts);
|
|
false -> string(String, sets:new(), Opts)
|
|
end.
|
|
|
|
-spec string(string(), sets:set(include_hash()), aeso_compiler:options()) -> parse_result().
|
|
string(String, Included, Opts) ->
|
|
AST = run_parser(file(), String, Opts),
|
|
case expand_includes(AST, Included, Opts) of
|
|
{ok, AST1} -> AST1;
|
|
{error, Err} -> parse_error(Err)
|
|
end.
|
|
|
|
|
|
run_parser(P, Inp) ->
|
|
escape_errors(parse_and_scan(P, Inp, [])).
|
|
run_parser(P, Inp, Opts) ->
|
|
escape_errors(parse_and_scan(P, Inp, Opts)).
|
|
|
|
parse_and_scan(P, S, Opts) ->
|
|
set_current_file(proplists:get_value(src_file, Opts, no_file)),
|
|
set_current_include_type(proplists:get_value(include_type, Opts, none)),
|
|
case aeso_scan:scan(S) of
|
|
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
|
|
{error, {{Input, Pos}, _}} ->
|
|
{error, {Pos, scan_error, Input}}
|
|
end.
|
|
|
|
-dialyzer({nowarn_function, parse_error/1}).
|
|
parse_error(Err) ->
|
|
aeso_errors:throw(mk_error(Err)).
|
|
|
|
mk_p_err(Pos, Msg) ->
|
|
aeso_errors:new(parse_error, mk_pos(Pos), lists:flatten(Msg)).
|
|
|
|
mk_error({Pos, scan_error, Input}) ->
|
|
mk_p_err(Pos, io_lib:format("Lexical error on input: ~s\n", [Input]));
|
|
mk_error({Pos, parse_error, Err}) ->
|
|
Msg = io_lib:format("~s\n", [Err]),
|
|
mk_p_err(Pos, Msg);
|
|
mk_error({Pos, ambiguous_parse, As}) ->
|
|
Msg = io_lib:format("Ambiguous parse result: ~p\n", [As]),
|
|
mk_p_err(Pos, Msg);
|
|
mk_error({Pos, include_error, File}) ->
|
|
Msg = io_lib:format("Couldn't find include file '~s'\n", [File]),
|
|
mk_p_err(Pos, Msg).
|
|
|
|
mk_pos({Line, Col}) -> aeso_errors:pos(Line, Col);
|
|
mk_pos({File, Line, Col}) -> aeso_errors:pos(File, Line, Col).
|
|
|
|
%% -- Parsing rules ----------------------------------------------------------
|
|
|
|
file() -> choice([], block(decl())).
|
|
|
|
decl() ->
|
|
?LAZY_P(
|
|
choice(
|
|
%% Contract declaration
|
|
[ ?RULE(token(main), keyword(contract),
|
|
con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5})
|
|
, ?RULE(keyword(contract),
|
|
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4})
|
|
, ?RULE(keyword(contract), token(interface),
|
|
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5})
|
|
, ?RULE(token(payable), token(main), keyword(contract),
|
|
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6}))
|
|
, ?RULE(token(payable), keyword(contract),
|
|
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5}))
|
|
, ?RULE(token(payable), keyword(contract), token(interface),
|
|
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6}))
|
|
|
|
|
|
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
|
|
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
|
|
, using()
|
|
, pragma()
|
|
|
|
%% Type declarations TODO: format annotation for "type bla" vs "type bla()"
|
|
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
|
|
, ?RULE(keyword(type), id(), type_vars(), {type_decl, _1, _2, _3})
|
|
, ?RULE(keyword(type), id(), tok('='), typedef(type), {type_def, _1, _2, [], _4})
|
|
, ?RULE(keyword(type), id(), type_vars(), tok('='), typedef(type), {type_def, _1, _2, _3, _5})
|
|
, ?RULE(keyword(record), id(), tok('='), typedef(record), {type_def, _1, _2, [], _4})
|
|
, ?RULE(keyword(record), id(), type_vars(), tok('='), typedef(record), {type_def, _1, _2, _3, _5})
|
|
, ?RULE(keyword(datatype), id(), tok('='), typedef(variant), {type_def, _1, _2, [], _4})
|
|
, ?RULE(keyword(datatype), id(), type_vars(), tok('='), typedef(variant), {type_def, _1, _2, _3, _5})
|
|
|
|
%% Function declarations
|
|
, ?RULE(modifiers(), fun_or_entry(), maybe_block(fundef_or_decl()), fun_block(_1, _2, _3))
|
|
, ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2))
|
|
])).
|
|
|
|
fun_block(Mods, Kind, [Decl]) ->
|
|
add_modifiers(Mods, Kind, set_pos(get_pos(Kind), Decl));
|
|
fun_block(Mods, Kind, Decls) ->
|
|
{block, get_ann(Kind), [ add_modifiers(Mods, Kind, Decl) || Decl <- Decls ]}.
|
|
|
|
fundef_or_decl() ->
|
|
choice([?RULE(id(), tok(':'), type(), {fun_decl, get_ann(_1), _1, _3}),
|
|
fundef()]).
|
|
|
|
using() ->
|
|
Alias = {keyword(as), con()},
|
|
For = ?RULE(keyword(for), bracket_list(id()), {for, _2}),
|
|
Hiding = ?RULE(keyword(hiding), bracket_list(id()), {hiding, _2}),
|
|
?RULE(keyword(using), con(), optional(Alias), optional(choice(For, Hiding)), using(get_ann(_1), _2, _3, _4)).
|
|
|
|
using(Ann, Con, none, none) ->
|
|
{using, Ann, Con, none, none};
|
|
using(Ann, Con, {ok, {_, Alias}}, none) ->
|
|
{using, Ann, Con, Alias, none};
|
|
using(Ann, Con, none, {ok, List}) ->
|
|
{using, Ann, Con, none, List};
|
|
using(Ann, Con, {ok, {_, Alias}}, {ok, List}) ->
|
|
{using, Ann, Con, Alias, List}.
|
|
|
|
pragma() ->
|
|
Op = choice([token(T) || T <- ['<', '=<', '==', '>=', '>']]),
|
|
?RULE(tok('@'), id("compiler"), Op, version(), {pragma, get_ann(_1), {compiler, element(1, _3), _4}}).
|
|
|
|
version() ->
|
|
?RULE(token(int), many({tok('.'), token(int)}), mk_version(_1, _2)).
|
|
|
|
mk_version({int, _, Maj}, Rest) ->
|
|
[Maj | [N || {_, {int, _, N}} <- Rest]].
|
|
|
|
fun_or_entry() ->
|
|
choice([?RULE(keyword(function), {function, _1}),
|
|
?RULE(keyword(entrypoint), {entrypoint, _1})]).
|
|
|
|
modifiers() ->
|
|
many(choice([token(stateful), token(payable), token(private), token(public)])).
|
|
|
|
add_modifiers(Mods, Entry = {entrypoint, _}, Node) ->
|
|
add_modifiers(Mods ++ [Entry], Node);
|
|
add_modifiers(Mods, {function, _}, Node) ->
|
|
add_modifiers(Mods, Node).
|
|
|
|
add_modifiers([], Node) -> Node;
|
|
add_modifiers(Mods = [Tok | _], Node) ->
|
|
%% Set the position to the position of the first modifier. This is
|
|
%% important for code transformation tools (like what we do in
|
|
%% create_calldata) to be able to get the indentation of the declaration.
|
|
set_pos(get_pos(Tok),
|
|
lists:foldl(fun({Mod, _}, X) -> set_ann(Mod, true, X) end,
|
|
Node, Mods)).
|
|
|
|
%% -- Type declarations ------------------------------------------------------
|
|
|
|
typedef(type) -> ?RULE(type(), {alias_t, _1});
|
|
typedef(record) -> ?RULE(brace_list(field_type()), {record_t, _1});
|
|
typedef(variant) -> ?RULE(constructors(), {variant_t, _1}).
|
|
|
|
constructors() ->
|
|
sep1(constructor(), tok('|')).
|
|
|
|
constructor() -> %% TODO: format for Con() vs Con
|
|
choice(?RULE(con(), {constr_t, get_ann(_1), _1, []}),
|
|
?RULE(con(), con_args(), {constr_t, get_ann(_1), _1, _2})).
|
|
|
|
con_args() -> paren_list(con_arg()).
|
|
type_args() -> paren_list(type()).
|
|
field_type() -> ?RULE(id(), tok(':'), type(), {field_t, get_ann(_1), _1, _3}).
|
|
|
|
con_arg() -> choice(type(), ?RULE(keyword(indexed), type(), set_ann(indexed, true, _2))).
|
|
|
|
%% -- Let declarations -------------------------------------------------------
|
|
|
|
letdecl() ->
|
|
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)).
|
|
|
|
letdef() -> choice(valdef(), fundef()).
|
|
|
|
valdef() ->
|
|
?RULE(pattern(), tok('='), body(), {letval, [], _1, _3}).
|
|
|
|
guarded_fundefs() ->
|
|
choice(
|
|
[ ?RULE(keyword('='), body(), [{guarded, _1, [], _2}])
|
|
, maybe_block(?RULE(keyword('|'), comma_sep(expr()), tok('='), body(), {guarded, _1, _2, _4}))
|
|
]).
|
|
|
|
fundef() ->
|
|
choice(
|
|
[ ?RULE(id(), args(), guarded_fundefs(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _3})
|
|
, ?RULE(id(), args(), tok(':'), type(), guarded_fundefs(), {letfun, get_ann(_1), _1, _2, _4, _5})
|
|
]).
|
|
|
|
args() -> paren_list(pattern()).
|
|
lam_args() -> paren_list(arg()).
|
|
|
|
arg() -> choice(
|
|
?RULE(id(), {arg, get_ann(_1), _1, type_wildcard(get_ann(_1))}),
|
|
?RULE(id(), tok(':'), type(), {arg, get_ann(_1), _1, _3})).
|
|
|
|
letpat() ->
|
|
?RULE(keyword('('), id(), tok('='), pattern(), tok(')'), {letpat, get_ann(_1), _2, _4}).
|
|
|
|
%% -- Types ------------------------------------------------------------------
|
|
|
|
type_vars() -> paren_list(tvar()).
|
|
|
|
type() -> ?LAZY_P(type100()).
|
|
|
|
type100() -> type200().
|
|
|
|
type200() ->
|
|
?RULE(many({type300(), keyword('=>')}), type300(), fun_t(_1, _2)).
|
|
|
|
type300() ->
|
|
?RULE(sep1(type400(), tok('*')), tuple_t(get_ann(lists:nth(1, _1)), _1)).
|
|
|
|
type400() ->
|
|
choice(
|
|
[?RULE(typeAtom(), optional(type_args()),
|
|
case _2 of
|
|
none -> _1;
|
|
{ok, Args} -> {app_t, get_ann(_1), _1, Args}
|
|
end),
|
|
?RULE(id("bytes"), parens(token(int)),
|
|
{bytes_t, get_ann(_1), element(3, _2)})
|
|
]).
|
|
|
|
typeAtom() ->
|
|
?LAZY_P(choice(
|
|
[ parens(type())
|
|
, args_t()
|
|
, id(), token(con), token(qcon), token(qid), tvar()
|
|
])).
|
|
|
|
args_t() ->
|
|
?LAZY_P(choice(
|
|
[ ?RULE(tok('('), tok(')'), {args_t, get_ann(_1), []})
|
|
%% Singleton case handled separately
|
|
, ?RULE(tok('('), type(), tok(','), sep1(type(), tok(',')), tok(')'), {args_t, get_ann(_1), [_2|_4]})
|
|
])).
|
|
|
|
%% -- Statements -------------------------------------------------------------
|
|
|
|
body() ->
|
|
?LET_P(Stmts, maybe_block(stmt()), block_e(Stmts)).
|
|
|
|
stmt() ->
|
|
?LAZY_P(choice(
|
|
[ using()
|
|
, expr()
|
|
, letdecl()
|
|
, {switch, keyword(switch), parens(expr()), maybe_block(branch())}
|
|
, {'if', keyword('if'), parens(expr()), body()}
|
|
, {elif, keyword(elif), parens(expr()), body()}
|
|
, {else, keyword(else), body()}
|
|
])).
|
|
|
|
branch() ->
|
|
?RULE(pattern(), guarded_branches(), {'case', get_ann(lists:nth(1, _2)), _1, _2}).
|
|
|
|
guarded_branches() ->
|
|
choice(
|
|
[ ?RULE(keyword('=>'), body(), [{guarded, _1, [], _2}])
|
|
, maybe_block(?RULE(tok('|'), comma_sep(expr()), keyword('=>'), body(), {guarded, _3, _2, _4}))
|
|
]).
|
|
|
|
pattern() ->
|
|
?LET_P(E, expr(), parse_pattern(E)).
|
|
|
|
%% -- Expressions ------------------------------------------------------------
|
|
|
|
expr() -> expr100().
|
|
|
|
expr100() ->
|
|
Expr100 = ?LAZY_P(expr100()),
|
|
Expr200 = ?LAZY_P(expr200()),
|
|
choice(
|
|
[ ?RULE(lam_args(), keyword('=>'), body(), {lam, _2, _1, _3}) %% TODO: better location
|
|
, {'if', keyword('if'), parens(Expr100), Expr200, right(tok(else), Expr100)}
|
|
, ?RULE(Expr200, optional(right(tok(':'), type())),
|
|
case _2 of
|
|
none -> _1;
|
|
{ok, Type} -> {typed, get_ann(_1), _1, Type}
|
|
end)
|
|
]).
|
|
|
|
expr200() -> infixr(expr300(), binop('||')).
|
|
expr300() -> infixr(expr400(), binop('&&')).
|
|
expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])).
|
|
expr500() -> infixr(expr600(), binop(['::', '++'])).
|
|
expr600() -> infixl(expr650(), binop(['+', '-'])).
|
|
expr650() -> ?RULE(many(token('-')), expr700(), prefixes(_1, _2)).
|
|
expr700() -> infixl(expr750(), binop(['*', '/', mod])).
|
|
expr750() -> infixl(expr800(), binop(['^'])).
|
|
expr800() -> ?RULE(many(token('!')), expr900(), prefixes(_1, _2)).
|
|
expr900() -> ?RULE(exprAtom(), many(elim()), elim(_1, _2)).
|
|
|
|
exprAtom() ->
|
|
?LAZY_P(begin
|
|
Expr = ?LAZY_P(expr()),
|
|
choice(
|
|
[ id_or_addr(), con(), token(qid), token(qcon)
|
|
, token(bytes), token(string), token(char)
|
|
, token(int)
|
|
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
|
|
, {bool, keyword(true), true}
|
|
, {bool, keyword(false), false}
|
|
, ?LET_P(Fs, brace_list(?LAZY_P(field_assignment())), record(Fs))
|
|
, {list, [], bracket_list(Expr)}
|
|
, ?RULE(keyword('['), Expr, token('|'), comma_sep(comprehension_exp()), tok(']'), list_comp_e(_1, _2, _4))
|
|
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
|
|
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
|
|
, letpat()
|
|
])
|
|
end).
|
|
|
|
comprehension_exp() ->
|
|
?LAZY_P(choice(
|
|
[ comprehension_bind()
|
|
, letdecl()
|
|
, comprehension_if()
|
|
])).
|
|
|
|
comprehension_if() ->
|
|
?RULE(keyword('if'), parens(expr()), {comprehension_if, _1, _2}).
|
|
|
|
comprehension_bind() ->
|
|
?RULE(pattern(), tok('<-'), expr(), {comprehension_bind, _1, _3}).
|
|
|
|
arg_expr() ->
|
|
?LAZY_P(
|
|
choice([ ?RULE(id(), tok('='), expr(), {named_arg, [], _1, _3})
|
|
, expr() ])).
|
|
|
|
elim() ->
|
|
?LAZY_P(
|
|
choice(
|
|
[ {proj, keyword('.'), id()}
|
|
, ?RULE(paren_list(arg_expr()), {app, [], _1})
|
|
, ?RULE(keyword('{'), comma_sep(field_assignment()), tok('}'), {rec_upd, _1, _2})
|
|
, ?RULE(keyword('['), map_key(), keyword(']'), map_get(_1, _2))
|
|
])).
|
|
|
|
map_get(Ann, {map_key, Key}) -> {map_get, Ann, Key};
|
|
map_get(Ann, {map_key, Key, Val}) -> {map_get, Ann, Key, Val}.
|
|
|
|
map_key() ->
|
|
?RULE(expr(), optional({tok('='), expr()}), map_key(_1, _2)).
|
|
|
|
map_key(Key, none) -> {map_key, Key};
|
|
map_key(Key, {ok, {_, Val}}) -> {map_key, Key, Val}.
|
|
|
|
elim(E, []) -> E;
|
|
elim(E, [{proj, Ann, P} | Es]) -> elim({proj, Ann, E, P}, Es);
|
|
elim(E, [{app, _Ann, Args} | Es]) -> elim({app, aeso_syntax:get_ann(E), E, Args}, Es);
|
|
elim(E, [{rec_upd, Ann, Flds} | Es]) -> elim(record_update(Ann, E, Flds), Es);
|
|
elim(E, [{map_get, Ann, Key} | Es]) -> elim({map_get, Ann, E, Key}, Es);
|
|
elim(E, [{map_get, Ann, Key, Val} | Es]) -> elim({map_get, Ann, E, Key, Val}, Es).
|
|
|
|
record_update(Ann, E, Flds) ->
|
|
{record_or_map(Flds), Ann, E, Flds}.
|
|
|
|
record([]) -> {map, [], []};
|
|
record(Fs) ->
|
|
case record_or_map(Fs) of
|
|
record ->
|
|
Fld = fun({field, _, [_], _} = F) -> F;
|
|
({field, Ann, LV, Id, _}) ->
|
|
bad_expr_err("Cannot use '@' in record construction", infix({lvalue, Ann, LV}, {'@', Ann}, Id));
|
|
({field, Ann, LV, _}) ->
|
|
bad_expr_err("Cannot use nested fields or keys in record construction", {lvalue, Ann, LV}) end,
|
|
{record, get_ann(hd(Fs)), lists:map(Fld, Fs)};
|
|
map ->
|
|
Ann = get_ann(hd(Fs ++ [{empty, []}])), %% TODO: source location for empty maps
|
|
KV = fun({field, _, [{map_get, _, Key}], Val}) -> {Key, Val};
|
|
({field, FAnn, LV, Id, _}) ->
|
|
bad_expr_err("Cannot use '@' in map construction", infix({lvalue, FAnn, LV}, {'@', Ann}, Id));
|
|
({field, FAnn, LV, _}) ->
|
|
bad_expr_err("Cannot use nested fields or keys in map construction", {lvalue, FAnn, LV}) end,
|
|
{map, Ann, lists:map(KV, Fs)};
|
|
record_or_map_error ->
|
|
{record_or_map_error, get_ann(hd(Fs)), Fs}
|
|
end.
|
|
|
|
record_or_map(Fields) ->
|
|
Kind = fun(Fld) -> case element(3, Fld) of
|
|
[{proj, _, _} | _] -> proj;
|
|
[{map_get, _, _} | _] -> map_get;
|
|
[{map_get, _, _, _} | _] -> map_get
|
|
end end,
|
|
case lists:usort(lists:map(Kind, Fields)) of
|
|
[proj] -> record;
|
|
[map_get] -> map;
|
|
_ -> record_or_map_error %% Defer error until type checking
|
|
end.
|
|
|
|
field_assignment() ->
|
|
?RULE(lvalue(), optional({tok('@'), id()}), tok('='), expr(), field_assignment(get_ann(_3), _1, _2, _4)).
|
|
|
|
field_assignment(Ann, LV, none, E) ->
|
|
{field, Ann, LV, E};
|
|
field_assignment(Ann, LV, {ok, {_, Id}}, E) ->
|
|
{field, Ann, LV, Id, E}.
|
|
|
|
lvalue() ->
|
|
?RULE(lvalueAtom(), many(elim()), lvalue(elim(_1, _2))).
|
|
|
|
lvalueAtom() ->
|
|
?LAZY_P(choice([ id()
|
|
, ?RULE(keyword('['), map_key(), keyword(']'), _2)
|
|
])).
|
|
|
|
lvalue(E) -> lvalue(E, []).
|
|
|
|
lvalue(X = {id, Ann, _}, LV) -> [{proj, Ann, X} | LV];
|
|
lvalue({map_key, K}, LV) -> [{map_get, get_ann(K), K} | LV];
|
|
lvalue({map_key, K, V}, LV) -> [{map_get, get_ann(K), K, V} | LV];
|
|
lvalue({proj, Ann, E, P}, LV) -> lvalue(E, [{proj, Ann, P} | LV]);
|
|
lvalue({map_get, Ann, E, K}, LV) -> lvalue(E, [{map_get, Ann, K} | LV]);
|
|
lvalue({map_get, Ann, E, K, V}, LV) -> lvalue(E, [{map_get, Ann, K, V} | LV]);
|
|
lvalue(E, _) -> bad_expr_err("Not a valid lvalue", E).
|
|
|
|
infix(E, Op) ->
|
|
?RULE(E, optional({Op, E}),
|
|
case _2 of
|
|
none -> _1;
|
|
{ok, {F, Arg}} -> F(_1, Arg)
|
|
end).
|
|
|
|
binop(Op) when is_atom(Op) -> binop([Op]);
|
|
binop(Ops) ->
|
|
?RULE(choice([ token(Op) || Op <- Ops ]), fun(A, B) -> infix(A, _1, B) end).
|
|
|
|
con() -> token(con).
|
|
id() -> token(id).
|
|
tvar() -> token(tvar).
|
|
str() -> token(string).
|
|
|
|
token(Tag) ->
|
|
?RULE(tok(Tag),
|
|
case _1 of
|
|
{Tok, {Line, Col}} -> {Tok, pos_ann(Line, Col)};
|
|
{Tok, {Line, Col}, Val} -> {Tok, pos_ann(Line, Col), Val}
|
|
end).
|
|
|
|
id(Id) ->
|
|
?LET_P({id, A, X} = Y, id(),
|
|
if X == Id -> Y;
|
|
true -> fail({A, "expected '" ++ Id ++ "'"})
|
|
end).
|
|
|
|
id_or_addr() ->
|
|
?RULE(id(), parse_addr_literal(_1)).
|
|
|
|
parse_addr_literal(Id = {id, Ann, Name}) ->
|
|
case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_"]) of
|
|
false -> Id;
|
|
true ->
|
|
try aeser_api_encoder:decode(list_to_binary(Name)) of
|
|
{Type, Bin} -> {Type, Ann, Bin}
|
|
catch _:_ ->
|
|
Id
|
|
end
|
|
end.
|
|
|
|
%% -- Helpers ----------------------------------------------------------------
|
|
|
|
keyword(K) -> ann(tok(K)).
|
|
ann(P) -> map(fun get_ann/1, P).
|
|
|
|
block(P) ->
|
|
between(layout(), sep1(P, tok(vsemi)), tok(vclose)).
|
|
|
|
maybe_block(P) ->
|
|
choice(block(P), [P]).
|
|
|
|
parens(P) -> between(tok('('), P, tok(')')).
|
|
braces(P) -> between(tok('{'), P, tok('}')).
|
|
brackets(P) -> between(tok('['), P, tok(']')).
|
|
comma_sep(P) -> sep(P, tok(',')).
|
|
|
|
paren_list(P) -> parens(comma_sep(P)).
|
|
brace_list(P) -> braces(comma_sep(P)).
|
|
bracket_list(P) -> brackets(comma_sep(P)).
|
|
|
|
%% -- Annotations ------------------------------------------------------------
|
|
|
|
-type ann() :: aeso_syntax:ann().
|
|
-type ann_line() :: aeso_syntax:ann_line().
|
|
-type ann_col() :: aeso_syntax:ann_col().
|
|
|
|
-spec pos_ann(ann_line(), ann_col()) -> ann().
|
|
pos_ann(Line, Col) ->
|
|
[ {file, current_file()}
|
|
, {include_type, current_include_type()}
|
|
, {line, Line}
|
|
, {col, Col} ].
|
|
|
|
ann_pos(Ann) ->
|
|
{proplists:get_value(file, Ann),
|
|
proplists:get_value(line, Ann),
|
|
proplists:get_value(col, Ann)}.
|
|
|
|
get_ann(Ann) when is_list(Ann) -> Ann;
|
|
get_ann(Node) ->
|
|
case element(2, Node) of
|
|
{Line, Col} when is_integer(Line), is_integer(Col) -> pos_ann(Line, Col);
|
|
Ann -> Ann
|
|
end.
|
|
|
|
get_ann(Key, Node) ->
|
|
proplists:get_value(Key, get_ann(Node)).
|
|
|
|
set_ann(Key, Val, Node) ->
|
|
Ann = get_ann(Node),
|
|
setelement(2, Node, lists:keystore(Key, 1, Ann, {Key, Val})).
|
|
|
|
get_pos(Node) ->
|
|
{current_file(), get_ann(line, Node), get_ann(col, Node)}.
|
|
|
|
set_pos({F, L, C}, Node) ->
|
|
set_ann(file, F, set_ann(line, L, set_ann(col, C, Node))).
|
|
|
|
infix(L, Op, R) -> set_ann(format, infix, {app, get_ann(L), Op, [L, R]}).
|
|
|
|
prefixes(Ops, E) -> lists:foldr(fun prefix/2, E, Ops).
|
|
prefix(Op, E) -> set_ann(format, prefix, {app, get_ann(Op), Op, [E]}).
|
|
|
|
type_wildcard(Ann) ->
|
|
{id, [{origin, system} | Ann], "_"}.
|
|
|
|
block_e(Stmts) ->
|
|
group_ifs(Stmts, []).
|
|
|
|
group_ifs([], [Stmt]) -> return(Stmt);
|
|
group_ifs([], Acc) ->
|
|
Stmts = [Stmt | _] = lists:reverse(Acc),
|
|
{block, get_ann(Stmt), Stmts};
|
|
group_ifs([{'if', Ann, Cond, Then} | Stmts], Acc) ->
|
|
{Elses, Rest} = else_branches(Stmts, []),
|
|
group_ifs(Rest, [build_if(Ann, Cond, Then, Elses) | Acc]);
|
|
group_ifs([{else, Ann, _} | _], _) ->
|
|
fail({Ann, "No matching 'if' for 'else'"});
|
|
group_ifs([{elif, Ann, _, _} | _], _) ->
|
|
fail({Ann, "No matching 'if' for 'elif'"});
|
|
group_ifs([Stmt | Stmts], Acc) ->
|
|
group_ifs(Stmts, [Stmt | Acc]).
|
|
|
|
build_if(Ann, Cond, Then, [{elif, Ann1, Cond1, Then1} | Elses]) ->
|
|
{'if', Ann, Cond, Then,
|
|
set_ann(format, elif, build_if(Ann1, Cond1, Then1, Elses))};
|
|
build_if(Ann, Cond, Then, [{else, _Ann, Else}]) ->
|
|
{'if', Ann, Cond, Then, Else};
|
|
build_if(Ann, Cond, Then, []) ->
|
|
{'if', Ann, Cond, Then, {tuple, [{origin, system}], []}}.
|
|
|
|
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
|
|
else_branches(Stmts, [Elif | Acc]);
|
|
else_branches([Else = {else, _, _} | Stmts], Acc) ->
|
|
{lists:reverse([Else | Acc]), Stmts};
|
|
else_branches(Stmts, Acc) ->
|
|
{lists:reverse(Acc), Stmts}.
|
|
|
|
tuple_t(_Ann, [Type]) -> Type; %% Not a tuple
|
|
tuple_t(Ann, Types) -> {tuple_t, Ann, Types}.
|
|
|
|
fun_t(Domains, Type) ->
|
|
lists:foldr(fun({{args_t, _, Dom}, Ann}, T) -> {fun_t, Ann, [], Dom, T};
|
|
({Dom, Ann}, T) -> {fun_t, Ann, [], [Dom], T} end,
|
|
Type, Domains).
|
|
|
|
tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple
|
|
tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
|
|
|
|
list_comp_e(Ann, Expr, Binds) -> {list_comp, Ann, Expr, Binds}.
|
|
|
|
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
|
|
parse_pattern({letpat, Ann, Id, Pat}) ->
|
|
{letpat, Ann, Id, parse_pattern(Pat)};
|
|
parse_pattern({app, Ann, Con = {'::', _}, Es}) ->
|
|
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
|
|
parse_pattern({app, Ann, {'-', _}, [{int, _, N}]}) ->
|
|
{int, Ann, -N};
|
|
parse_pattern({app, Ann, Con = {Tag, _, _}, Es}) when Tag == con; Tag == qcon ->
|
|
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
|
|
parse_pattern({tuple, Ann, Es}) ->
|
|
{tuple, Ann, lists:map(fun parse_pattern/1, Es)};
|
|
parse_pattern({list, Ann, Es}) ->
|
|
{list, Ann, lists:map(fun parse_pattern/1, Es)};
|
|
parse_pattern({record, Ann, Fs}) ->
|
|
{record, Ann, lists:map(fun parse_field_pattern/1, Fs)};
|
|
parse_pattern({typed, Ann, E, Type}) ->
|
|
{typed, Ann, parse_pattern(E), Type};
|
|
parse_pattern(E = {con, _, _}) -> E;
|
|
parse_pattern(E = {qcon, _, _}) -> E;
|
|
parse_pattern(E = {id, _, _}) -> E;
|
|
parse_pattern(E = {int, _, _}) -> E;
|
|
parse_pattern(E = {bool, _, _}) -> E;
|
|
parse_pattern(E = {bytes, _, _}) -> E;
|
|
parse_pattern(E = {string, _, _}) -> E;
|
|
parse_pattern(E = {char, _, _}) -> E;
|
|
parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
|
|
|
|
-spec parse_field_pattern(aeso_syntax:field(aeso_syntax:expr())) -> aeso_parse_lib:parser(aeso_syntax:field(aeso_syntax:pat())).
|
|
parse_field_pattern({field, Ann, F, E}) ->
|
|
{field, Ann, F, parse_pattern(E)}.
|
|
|
|
-spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()).
|
|
ret_doc_err(Ann, Doc) ->
|
|
fail(ann_pos(Ann), prettypr:format(Doc)).
|
|
|
|
-spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()).
|
|
bad_expr_err(Reason, E) ->
|
|
ret_doc_err(get_ann(E),
|
|
prettypr:sep([prettypr:text(Reason ++ ":"),
|
|
prettypr:nest(2, aeso_pretty:expr(E))])).
|
|
|
|
%% -- Helper functions -------------------------------------------------------
|
|
|
|
expand_includes(AST, Included, Opts) ->
|
|
Ann = [{origin, system}],
|
|
AST1 = [ {include, Ann, {string, Ann, File}}
|
|
|| File <- lists:usort(auto_imports(AST)) ] ++ AST,
|
|
expand_includes(AST1, Included, [], Opts).
|
|
|
|
expand_includes([], Included, Acc, Opts) ->
|
|
case lists:member(keep_included, Opts) of
|
|
false ->
|
|
{ok, lists:reverse(Acc)};
|
|
true ->
|
|
{ok, {lists:reverse(Acc), Included}}
|
|
end;
|
|
expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) ->
|
|
case get_include_code(File, Ann, Opts) of
|
|
{ok, Code} ->
|
|
Hashed = hash_include(File, Code),
|
|
case sets:is_element(Hashed, Included) of
|
|
false ->
|
|
IncludeType = case proplists:get_value(file, Ann) of
|
|
no_file -> direct;
|
|
_ -> indirect
|
|
end,
|
|
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
|
|
Opts2 = lists:keystore(include_type, 1, Opts1, {include_type, IncludeType}),
|
|
Included1 = sets:add_element(Hashed, Included),
|
|
case parse_and_scan(file(), Code, Opts2) of
|
|
{ok, AST1} ->
|
|
expand_includes(AST1 ++ AST, Included1, Acc, Opts);
|
|
Err = {error, _} ->
|
|
Err
|
|
end;
|
|
true ->
|
|
expand_includes(AST, Included, Acc, Opts)
|
|
end;
|
|
Err = {error, _} ->
|
|
Err
|
|
end;
|
|
expand_includes([E | AST], Included, Acc, Opts) ->
|
|
expand_includes(AST, Included, [E | Acc], Opts).
|
|
|
|
read_file(File, Opts) ->
|
|
case proplists:get_value(include, Opts, {explicit_files, #{}}) of
|
|
{file_system, Paths} ->
|
|
CandidateNames = [ filename:join(Dir, File) || Dir <- Paths ],
|
|
lists:foldr(fun(F, {error, _}) -> file:read_file(F);
|
|
(_F, OK) -> OK end, {error, not_found}, CandidateNames);
|
|
{explicit_files, Files} ->
|
|
case maps:get(binary_to_list(File), Files, not_found) of
|
|
not_found -> {error, not_found};
|
|
Src -> {ok, Src}
|
|
end;
|
|
escript ->
|
|
try
|
|
Escript = escript:script_name(),
|
|
{ok, Sections} = escript:extract(Escript, []),
|
|
Archive = proplists:get_value(archive, Sections),
|
|
FileName = binary_to_list(filename:join([aesophia, priv, stdlib, File])),
|
|
case zip:extract(Archive, [{file_list, [FileName]}, memory]) of
|
|
{ok, [{_, Src}]} -> {ok, Src};
|
|
_ -> {error, not_found}
|
|
end
|
|
catch _:_ ->
|
|
{error, not_found}
|
|
end
|
|
end.
|
|
|
|
stdlib_options() ->
|
|
StdLibDir = aeso_stdlib:stdlib_include_path(),
|
|
case filelib:is_dir(StdLibDir) of
|
|
true -> [{include, {file_system, [StdLibDir]}}];
|
|
false -> [{include, escript}]
|
|
end.
|
|
|
|
get_include_code(File, Ann, Opts) ->
|
|
case {read_file(File, Opts), read_file(File, stdlib_options())} of
|
|
{{ok, Bin}, {ok, _}} ->
|
|
case filename:basename(File) == File of
|
|
true -> { error
|
|
, fail( ann_pos(Ann)
|
|
, "Illegal redefinition of standard library " ++ binary_to_list(File))};
|
|
%% If a path is provided then the stdlib takes lower priority
|
|
false -> {ok, binary_to_list(Bin)}
|
|
end;
|
|
{_, {ok, Bin}} ->
|
|
{ok, binary_to_list(Bin)};
|
|
{{ok, Bin}, _} ->
|
|
{ok, binary_to_list(Bin)};
|
|
{_, _} ->
|
|
{error, {ann_pos(Ann), include_error, File}}
|
|
end.
|
|
|
|
-spec hash_include(string() | binary(), string()) -> include_hash().
|
|
hash_include(File, Code) when is_binary(File) ->
|
|
hash_include(binary_to_list(File), Code);
|
|
hash_include(File, Code) when is_list(File) ->
|
|
{filename:basename(File), crypto:hash(sha256, Code)}.
|
|
|
|
auto_imports({comprehension_bind, _, _}) -> [<<"ListInternal.aes">>];
|
|
auto_imports({'..', _}) -> [<<"ListInternal.aes">>];
|
|
auto_imports(L) when is_list(L) ->
|
|
lists:flatmap(fun auto_imports/1, L);
|
|
auto_imports(T) when is_tuple(T) ->
|
|
auto_imports(tuple_to_list(T));
|
|
auto_imports(_) -> [].
|