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:
parent
0a5b80668f
commit
2b7490776e
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,3 +18,4 @@ _build
|
|||||||
rebar3.crashdump
|
rebar3.crashdump
|
||||||
*.erl~
|
*.erl~
|
||||||
*.aes~
|
*.aes~
|
||||||
|
aesophia
|
||||||
|
@ -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]).
|
||||||
|
|
||||||
|
@ -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}.
|
||||||
|
@ -5,28 +5,37 @@
|
|||||||
-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) ->
|
||||||
case aeso_scan:scan(S) of
|
set_current_file(proplists:get_value(src_file, Opts, no_file)),
|
||||||
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
|
case aeso_scan:scan(S) of
|
||||||
Error -> Error
|
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
|
||||||
end.
|
Error -> Error
|
||||||
|
end.
|
||||||
|
|
||||||
%% -- Parsing rules ----------------------------------------------------------
|
%% -- Parsing rules ----------------------------------------------------------
|
||||||
|
|
||||||
@ -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).
|
||||||
|
|
||||||
|
@ -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, "|"),
|
||||||
|
|
||||||
|
@ -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
|
||||||
check_errors(lists:sort(ExpectedErrors), ErrorString)
|
<<"Type errors\n", ErrorString/binary>> ->
|
||||||
|
check_errors(lists:sort(ExpectedErrors), ErrorString);
|
||||||
|
<<"Parse errors\n", ErrorString/binary>> ->
|
||||||
|
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">>]}
|
||||||
].
|
].
|
||||||
|
10
test/contracts/include.aes
Normal file
10
test/contracts/include.aes
Normal 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()
|
2
test/contracts/included.aes
Normal file
2
test/contracts/included.aes
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
namespace Included =
|
||||||
|
function foo() = 42
|
5
test/contracts/included2.aes
Normal file
5
test/contracts/included2.aes
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
namespace Included2a =
|
||||||
|
function bar() = 43
|
||||||
|
|
||||||
|
namespace Included2b =
|
||||||
|
function foo() = 44
|
Loading…
x
Reference in New Issue
Block a user