Add include directive

Add an include directive to include namespaces into a contract. Only allowed at the top level.

To allow includes, either call through aeso_compiler:file or set the option `allow_include` (and add `include_path`(s)).
This commit is contained in:
Hans Svensson 2019-02-05 08:52:40 +01:00 committed by Ulf Norell
parent 0a5b80668f
commit 2b7490776e
9 changed files with 139 additions and 40 deletions

1
.gitignore vendored
View File

@ -18,3 +18,4 @@ _build
rebar3.crashdump rebar3.crashdump
*.erl~ *.erl~
*.aes~ *.aes~
aesophia

View File

@ -21,8 +21,16 @@
-include("aeso_icode.hrl"). -include("aeso_icode.hrl").
-type option() :: pp_sophia_code | pp_ast | pp_types | pp_typed_ast | -type option() :: pp_sophia_code
pp_icode| pp_assembler | pp_bytecode. | pp_ast
| pp_types
| pp_typed_ast
| pp_icode
| pp_assembler
| pp_bytecode
| {include_path, [string()]}
| {allow_include, boolean()}
| {src_file, string()}.
-type options() :: [option()]. -type options() :: [option()].
-export_type([ option/0 -export_type([ option/0
@ -40,12 +48,13 @@ version() ->
-spec file(string()) -> {ok, map()} | {error, binary()}. -spec file(string()) -> {ok, map()} | {error, binary()}.
file(Filename) -> file(Filename) ->
file(Filename, []). Dir = filename:dirname(Filename),
file(Filename, [{include_path, [Dir]}]).
-spec file(string(), options()) -> {ok, map()} | {error, binary()}. -spec file(string(), options()) -> {ok, map()} | {error, binary()}.
file(File, Options) -> file(File, Options) ->
case read_contract(File) of case read_contract(File) of
{ok, Bin} -> from_string(Bin, Options); {ok, Bin} -> from_string(Bin, [{src_file, File}, {allow_include, true} | Options]);
{error, Error} -> {error, Error} ->
ErrorString = [File,": ",file:format_error(Error)], ErrorString = [File,": ",file:format_error(Error)],
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)} {error, join_errors("File errors", [ErrorString], fun(E) -> E end)}
@ -213,9 +222,6 @@ icode_to_term(T, V) ->
icodes_to_terms(Ts, Vs) -> icodes_to_terms(Ts, Vs) ->
[ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ]. [ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ].
parse(C,_Options) ->
parse_string(C).
to_icode(TypedAst, Options) -> to_icode(TypedAst, Options) ->
aeso_ast_to_icode:convert_typed(TypedAst, Options). aeso_ast_to_icode:convert_typed(TypedAst, Options).
@ -265,9 +271,9 @@ sophia_type_to_typerep(String) ->
catch _:_ -> {error, bad_type} catch _:_ -> {error, bad_type}
end. end.
parse_string(Text) -> parse(Text, Options) ->
%% Try and return something sensible here! %% Try and return something sensible here!
case aeso_parser:string(Text) of case aeso_parser:string(Text, Options) of
%% Yay, it worked! %% Yay, it worked!
{ok, Contract} -> Contract; {ok, Contract} -> Contract;
%% Scan errors. %% Scan errors.
@ -280,12 +286,25 @@ parse_string(Text) ->
parse_error(Pos, Error); parse_error(Pos, Error);
{error, {Pos, ambiguous_parse, As}} -> {error, {Pos, ambiguous_parse, As}} ->
ErrorString = io_lib:format("Ambiguous ~p", [As]), ErrorString = io_lib:format("Ambiguous ~p", [As]),
parse_error(Pos, ErrorString) parse_error(Pos, ErrorString);
%% Include error
{error, {Pos, include_not_allowed}} ->
parse_error(Pos, "includes not allowed in this context");
{error, {Pos, include_error}} ->
parse_error(Pos, "could not find include file")
end. end.
parse_error({Line, Pos}, ErrorString) -> parse_error(Pos, ErrorString) ->
Error = io_lib:format("line ~p, column ~p: ~s", [Line, Pos, ErrorString]), Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}). error({parse_errors, [Error]}).
read_contract(Name) -> read_contract(Name) ->
file:read_file(Name). file:read_file(Name).
pos_error({Line, Pos}) ->
io_lib:format("line ~p, column ~p", [Line, Pos]);
pos_error({no_file, Line, Pos}) ->
pos_error({Line, Pos});
pos_error({File, Line, Pos}) ->
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).

View File

@ -19,7 +19,7 @@
-export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]). -export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]).
-type pos() :: {integer(), integer()}. -type pos() :: {string() | no_file, integer(), integer()} | {integer(), integer()}.
-type token() :: {atom(), pos(), term()} | {atom(), pos()}. -type token() :: {atom(), pos(), term()} | {atom(), pos()}.
-type tokens() :: [token()]. -type tokens() :: [token()].
-type error() :: {pos(), string() | no_error}. -type error() :: {pos(), string() | no_error}.

View File

@ -5,24 +5,33 @@
-module(aeso_parser). -module(aeso_parser).
-export([string/1, -export([string/1,
string/2,
type/1]). type/1]).
-include("aeso_parse_lib.hrl"). -include("aeso_parse_lib.hrl").
-spec string(string()) -> -type parse_result() :: {ok, aeso_syntax:ast()}
{ok, aeso_syntax:ast()} | {error, {aeso_parse_lib:pos(), atom(), term()}}
| {error, {aeso_parse_lib:pos(), | {error, {aeso_parse_lib:pos(), atom()}}.
atom(),
term()}} -spec string(string()) -> parse_result().
| {error, {aeso_parse_lib:pos(),
atom()}}.
string(String) -> string(String) ->
parse_and_scan(file(), String). string(String, []).
-spec string(string(), aeso_compiler:options()) -> parse_result().
string(String, Opts) ->
case parse_and_scan(file(), String, Opts) of
{ok, AST} ->
expand_includes(AST, Opts);
Err = {error, _} ->
Err
end.
type(String) -> type(String) ->
parse_and_scan(type(), String). parse_and_scan(type(), String, []).
parse_and_scan(P, S) -> parse_and_scan(P, S, Opts) ->
set_current_file(proplists:get_value(src_file, Opts, no_file)),
case aeso_scan:scan(S) of case aeso_scan:scan(S) of
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens); {ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
Error -> Error Error -> Error
@ -38,6 +47,7 @@ decl() ->
%% Contract declaration %% Contract declaration
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4}) [ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4}) , ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
, ?RULE(keyword(include), str(), {include, _2})
%% Type declarations TODO: format annotation for "type bla" vs "type bla()" %% Type declarations TODO: format annotation for "type bla" vs "type bla()"
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []}) , ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
@ -302,6 +312,7 @@ binop(Ops) ->
con() -> token(con). con() -> token(con).
id() -> token(id). id() -> token(id).
tvar() -> token(tvar). tvar() -> token(tvar).
str() -> token(string).
token(Tag) -> token(Tag) ->
?RULE(tok(Tag), ?RULE(tok(Tag),
@ -337,10 +348,17 @@ bracket_list(P) -> brackets(comma_sep(P)).
-type ann_col() :: aeso_syntax:ann_col(). -type ann_col() :: aeso_syntax:ann_col().
-spec pos_ann(ann_line(), ann_col()) -> ann(). -spec pos_ann(ann_line(), ann_col()) -> ann().
pos_ann(Line, Col) -> [{line, Line}, {col, Col}]. pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
current_file() ->
get('$current_file').
set_current_file(File) ->
put('$current_file', File).
ann_pos(Ann) -> ann_pos(Ann) ->
{proplists:get_value(line, Ann), {proplists:get_value(file, Ann),
proplists:get_value(line, Ann),
proplists:get_value(col, Ann)}. proplists:get_value(col, Ann)}.
get_ann(Ann) when is_list(Ann) -> Ann; get_ann(Ann) when is_list(Ann) -> Ann;
@ -358,10 +376,10 @@ set_ann(Key, Val, Node) ->
setelement(2, Node, lists:keystore(Key, 1, Ann, {Key, Val})). setelement(2, Node, lists:keystore(Key, 1, Ann, {Key, Val})).
get_pos(Node) -> get_pos(Node) ->
{get_ann(line, Node), get_ann(col, Node)}. {current_file(), get_ann(line, Node), get_ann(col, Node)}.
set_pos({L, C}, Node) -> set_pos({F, L, C}, Node) ->
set_ann(line, L, set_ann(col, 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]}). infix(L, Op, R) -> set_ann(format, infix, {app, get_ann(L), Op, [L, R]}).
@ -443,8 +461,10 @@ parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
parse_field_pattern({field, Ann, F, E}) -> parse_field_pattern({field, Ann, F, E}) ->
{field, Ann, F, parse_pattern(E)}. {field, Ann, F, parse_pattern(E)}.
return_error({L, C}, Err) -> return_error({no_file, L, C}, Err) ->
fail(io_lib:format("~p:~p:\n~s", [L, C, Err])). fail(io_lib:format("~p:~p:\n~s", [L, C, Err]));
return_error({F, L, C}, Err) ->
fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])).
-spec ret_doc_err(ann(), prettypr:document()) -> no_return(). -spec ret_doc_err(ann(), prettypr:document()) -> no_return().
ret_doc_err(Ann, Doc) -> ret_doc_err(Ann, Doc) ->
@ -456,3 +476,34 @@ bad_expr_err(Reason, E) ->
prettypr:sep([prettypr:text(Reason ++ ":"), prettypr:sep([prettypr:text(Reason ++ ":"),
prettypr:nest(2, aeso_pretty:expr(E))])). prettypr:nest(2, aeso_pretty:expr(E))])).
%% -- Helper functions -------------------------------------------------------
expand_includes(AST, Opts) ->
expand_includes(AST, [], Opts).
expand_includes([], Acc, _Opts) ->
{ok, lists:reverse(Acc)};
expand_includes([{include, S = {string, _, File}} | AST], Acc, Opts) ->
AllowInc = proplists:get_value(allow_include, Opts, false),
case read_file(File, Opts) of
{ok, Bin} when AllowInc ->
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
case string(binary_to_list(Bin), Opts1) of
{ok, AST1} ->
expand_includes(AST1 ++ AST, Acc, Opts);
Err = {error, _} ->
Err
end;
{ok, _} ->
{error, {get_pos(S), include_not_allowed}};
{error, _} ->
{error, {get_pos(S), include_error}}
end;
expand_includes([E | AST], Acc, Opts) ->
expand_includes(AST, [E | Acc], Opts).
read_file(File, Opts) ->
CandidateNames = [File] ++ [ filename:join(Dir, File)
|| Dir <- proplists:get_value(include_path, Opts, []) ],
lists:foldr(fun(F, {error, _}) -> file:read_file(F);
(_F, OK) -> OK end, {error, not_found}, CandidateNames).

View File

@ -36,7 +36,7 @@ lexer() ->
, {"\\*/", pop(skip())} , {"\\*/", pop(skip())}
, {"[^/*]+|[/*]", skip()} ], , {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "import", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function", Keywords = ["contract", "include", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"], "stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"],
KW = string:join(Keywords, "|"), KW = string:join(Keywords, "|"),

View File

@ -23,8 +23,12 @@ simple_compile_test_() ->
end} || ContractName <- compilable_contracts() ] ++ end} || ContractName <- compilable_contracts() ] ++
[ {"Testing error messages of " ++ ContractName, [ {"Testing error messages of " ++ ContractName,
fun() -> fun() ->
<<"Type errors\n",ErrorString/binary>> = compile(ContractName), case compile(ContractName, false) of
<<"Type errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString);
<<"Parse errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString) check_errors(lists:sort(ExpectedErrors), ErrorString)
end
end} || end} ||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++ {ContractName, ExpectedErrors} <- failing_contracts() ] ++
[ {"Testing deadcode elimination", [ {"Testing deadcode elimination",
@ -46,9 +50,13 @@ check_errors(Expect, ErrorString) ->
{Missing, Extra} -> ?assertEqual(Missing, Extra) {Missing, Extra} -> ?assertEqual(Missing, Extra)
end. end.
compile(Name) -> compile(Name) -> compile(Name, true).
compile(Name, AllowInc) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, []) of case aeso_compiler:from_string(String, [{include_path, [aeso_test_utils:contract_path()]},
{allow_include, AllowInc},
{src_file, Name}]) of
{ok,Map} -> Map; {ok,Map} -> Map;
{error,ErrorString} -> ErrorString {error,ErrorString} -> ErrorString
end. end.
@ -78,7 +86,8 @@ compilable_contracts() ->
"deadcode", "deadcode",
"variant_types", "variant_types",
"state_handling", "state_handling",
"events" "events",
"include"
]. ].
%% Contracts that should produce type errors %% Contracts that should produce type errors
@ -192,4 +201,6 @@ failing_contracts() ->
" r.foo : (gas : int, value : int) => Remote.themap\n" " r.foo : (gas : int, value : int) => Remote.themap\n"
"against the expected type\n" "against the expected type\n"
" (gas : int, value : int) => map(string, int)">>]} " (gas : int, value : int) => map(string, int)">>]}
, {"include",
[<<"file include, line 1, column 9: includes not allowed in this context\n">>]}
]. ].

View File

@ -0,0 +1,10 @@
include "included.aes"
include "../contracts/included2.aes"
contract Include =
// include "maps.aes"
function foo() =
Included.foo() < Included2a.bar()
function bar() =
Included2b.foo() > Included.foo()

View File

@ -0,0 +1,2 @@
namespace Included =
function foo() = 42

View File

@ -0,0 +1,5 @@
namespace Included2a =
function bar() = 43
namespace Included2b =
function foo() = 44