Implement namespaces

This includes a massive refactoring of the type checker, getting
rid of most of the ets tables and keeping a proper environment.
This commit is contained in:
Ulf Norell 2019-01-21 14:20:57 +01:00
parent 026ff52528
commit 367f87b612
14 changed files with 863 additions and 457 deletions

File diff suppressed because it is too large Load Diff

View File

@ -19,9 +19,13 @@
convert_typed(TypedTree, Options) -> convert_typed(TypedTree, Options) ->
code(TypedTree, aeso_icode:new(Options)). code(TypedTree, aeso_icode:new(Options)).
code([{contract, _Attribs, {con, _, Name}, Code}|Rest], Icode) -> code([{contract, _Attribs, Con = {con, _, Name}, Code}|Rest], Icode) ->
NewIcode = contract_to_icode(Code, NewIcode = contract_to_icode(Code,
aeso_icode:set_name(Name, Icode)), aeso_icode:set_namespace(Con,
aeso_icode:set_name(Name, Icode))),
code(Rest, NewIcode);
code([{namespace, _Ann, Name, Code}|Rest], Icode) ->
NewIcode = contract_to_icode(Code, aeso_icode:enter_namespace(Name, Icode)),
code(Rest, NewIcode); code(Rest, NewIcode);
code([], Icode) -> code([], Icode) ->
add_default_init_function(add_builtins(Icode)). add_default_init_function(add_builtins(Icode)).
@ -33,18 +37,24 @@ gen_error(Error) ->
%% Create default init function (only if state is unit). %% Create default init function (only if state is unit).
add_default_init_function(Icode = #{functions := Funs, state_type := State}) -> add_default_init_function(Icode = #{functions := Funs, state_type := State}) ->
case lists:keymember("init", 1, Funs) of {_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode),
case lists:keymember(QInit, 1, Funs) of
true -> Icode; true -> Icode;
false when State /= {tuple, []} -> gen_error(missing_init_function); false when State /= {tuple, []} ->
gen_error(missing_init_function);
false -> false ->
Type = {tuple, [typerep, {tuple, []}]}, Type = {tuple, [typerep, {tuple, []}]},
Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] }, Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] },
DefaultInit = {"init", [], [], Value, Type}, DefaultInit = {QInit, [], [], Value, Type},
Icode#{ functions => [DefaultInit | Funs] } Icode#{ functions => [DefaultInit | Funs] }
end. end.
-spec contract_to_icode(aeso_syntax:ast(), aeso_icode:icode()) -> -spec contract_to_icode(aeso_syntax:ast(), aeso_icode:icode()) ->
aeso_icode:icode(). aeso_icode:icode().
contract_to_icode([{namespace, _, Name, Defs} | Rest], Icode) ->
NS = aeso_icode:get_namespace(Icode),
Icode1 = contract_to_icode(Defs, aeso_icode:enter_namespace(Name, Icode)),
contract_to_icode(Rest, aeso_icode:set_namespace(NS, Icode1));
contract_to_icode([{type_def, _Attrib, {id, _, Name}, Args, Def} | Rest], contract_to_icode([{type_def, _Attrib, {id, _, Name}, Args, Def} | Rest],
Icode = #{ types := Types, constructors := Constructors }) -> Icode = #{ types := Types, constructors := Constructors }) ->
TypeDef = make_type_def(Args, Def, Icode), TypeDef = make_type_def(Args, Def, Icode),
@ -52,8 +62,9 @@ contract_to_icode([{type_def, _Attrib, {id, _, Name}, Args, Def} | Rest],
case Def of case Def of
{variant_t, Cons} -> {variant_t, Cons} ->
Tags = lists:seq(0, length(Cons) - 1), Tags = lists:seq(0, length(Cons) - 1),
GetName = fun({constr_t, _, {con, _, C}, _}) -> C end, GetName = fun({constr_t, _, C, _}) -> C end,
maps:from_list([ {GetName(Con), Tag} || {Tag, Con} <- lists:zip(Tags, Cons) ]); QName = fun(Con) -> {_, _, Xs} = aeso_icode:qualify(GetName(Con), Icode), Xs end,
maps:from_list([ {QName(Con), Tag} || {Tag, Con} <- lists:zip(Tags, Cons) ]);
_ -> #{} _ -> #{}
end, end,
Icode1 = Icode#{ types := Types#{ Name => TypeDef }, Icode1 = Icode#{ types := Types#{ Name => TypeDef },
@ -84,7 +95,8 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
{tuple, [typerep, ast_typerep(T, Icode)]}}; {tuple, [typerep, ast_typerep(T, Icode)]}};
_ -> {ast_body(Body, Icode), ast_typerep(T, Icode)} _ -> {ast_body(Body, Icode), ast_typerep(T, Icode)}
end, end,
NewIcode = ast_fun_to_icode(FunName, FunAttrs, FunArgs, FunBody, TypeRep, Icode), QName = aeso_icode:qualify(Name, Icode),
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode),
contract_to_icode(Rest, NewIcode); contract_to_icode(Rest, NewIcode);
contract_to_icode([{letrec,_,Defs}|Rest], Icode) -> contract_to_icode([{letrec,_,Defs}|Rest], Icode) ->
%% OBS! This code ignores the letrec structure of the source, %% OBS! This code ignores the letrec structure of the source,
@ -98,7 +110,8 @@ contract_to_icode(_Code, Icode) ->
%% TODO debug output for debug("Unhandled code ~p~n",[Code]), %% TODO debug output for debug("Unhandled code ~p~n",[Code]),
Icode. Icode.
ast_id({id, _, Id}) -> Id. ast_id({id, _, Id}) -> Id;
ast_id({qid, _, Id}) -> Id.
ast_args([{arg, _, Name, Type}|Rest], Acc, Icode) -> ast_args([{arg, _, Name, Type}|Rest], Acc, Icode) ->
ast_args(Rest, [{ast_id(Name), ast_type(Type, Icode)}| Acc], Icode); ast_args(Rest, [{ast_id(Name), ast_type(Type, Icode)}| Acc], Icode);
@ -384,7 +397,8 @@ ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
%% Other terms %% Other terms
ast_body({id, _, Name}, _Icode) -> ast_body({id, _, Name}, _Icode) ->
%% TODO Look up id in env #var_ref{name = Name};
ast_body({qid, _, Name}, _Icode) ->
#var_ref{name = Name}; #var_ref{name = Name};
ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
Value = if Bool -> 1 ; true -> 0 end, Value = if Bool -> 1 ; true -> 0 end,
@ -441,9 +455,15 @@ ast_body({proj, _, {typed, _, _, {con, _, Contract}}, {id, _, FunName}}, _Icode)
string:join([Contract, FunName], ".")}); string:join([Contract, FunName], ".")});
ast_body({con, _, Name}, Icode) -> ast_body({con, _, Name}, Icode) ->
Tag = aeso_icode:get_constructor_tag([Name], Icode),
#tuple{cpts = [#integer{value = Tag}]};
ast_body({qcon, _, Name}, Icode) ->
Tag = aeso_icode:get_constructor_tag(Name, Icode), Tag = aeso_icode:get_constructor_tag(Name, Icode),
#tuple{cpts = [#integer{value = Tag}]}; #tuple{cpts = [#integer{value = Tag}]};
ast_body({app, _, {typed, _, {con, _, Name}, _}, Args}, Icode) -> ast_body({app, _, {typed, _, {con, _, Name}, _}, Args}, Icode) ->
Tag = aeso_icode:get_constructor_tag([Name], Icode),
#tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]};
ast_body({app, _, {typed, _, {qcon, _, Name}, _}, Args}, Icode) ->
Tag = aeso_icode:get_constructor_tag(Name, Icode), Tag = aeso_icode:get_constructor_tag(Name, Icode),
#tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]}; #tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]};
ast_body({app,As,Fun,Args}, Icode) -> ast_body({app,As,Fun,Args}, Icode) ->

View File

@ -152,7 +152,7 @@ create_calldata(Contract, Function, Argument) when is_map(Contract) ->
[FunName | _] -> [FunName | _] ->
Args = lists:map(fun($\n) -> 32; (X) -> X end, Argument), %% newline to space Args = lists:map(fun($\n) -> 32; (X) -> X end, Argument), %% newline to space
CallContract = lists:flatten( CallContract = lists:flatten(
[ "contract Call =\n" [ "contract MakeCall =\n"
, " function ", Function, "\n" , " function ", Function, "\n"
, " function __call() = ", FunName, "(", Args, ")" , " function __call() = ", FunName, "(", Args, ")"
]), ]),
@ -161,15 +161,15 @@ create_calldata(Contract, Function, Argument) when is_map(Contract) ->
get_arg_icode(Funs) -> get_arg_icode(Funs) ->
[Args] = [ Args || {?CALL_NAME, _, _, {funcall, _, Args}, _} <- Funs ], [Args] = [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ],
Args. Args.
get_call_type([{contract, _, _, Defs}]) -> get_call_type([{contract, _, _, Defs}]) ->
case [ {FunName, FunType} case [ {lists:last(QFunName), FunType}
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret, || {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
{typed, _, {typed, _,
{app, _, {app, _,
{typed, _, {id, _, FunName}, FunType}, _}, _}} <- Defs ] of {typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
[Call] -> {ok, Call}; [Call] -> {ok, Call};
[] -> {error, missing_call_function} [] -> {error, missing_call_function}
end; end;
@ -228,7 +228,7 @@ to_bytecode([Op|Rest], Options) ->
to_bytecode([], _) -> []. to_bytecode([], _) -> [].
extract_type_info(#{functions := Functions} =_Icode) -> extract_type_info(#{functions := Functions} =_Icode) ->
TypeInfo = [aeso_abi:function_type_info(list_to_binary(Name), Args, TypeRep) TypeInfo = [aeso_abi:function_type_info(list_to_binary(lists:last(Name)), Args, TypeRep)
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions, || {Name, Attrs, Args,_Body, TypeRep} <- Functions,
not is_tuple(Name), not is_tuple(Name),
not lists:member(private, Attrs) not lists:member(private, Attrs)

View File

@ -9,7 +9,18 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeso_icode). -module(aeso_icode).
-export([new/1, pp/1, set_name/2, set_functions/2, map_typerep/2, option_typerep/1, get_constructor_tag/2]). -export([new/1,
pp/1,
set_name/2,
set_namespace/2,
enter_namespace/2,
get_namespace/1,
qualify/2,
set_functions/2,
map_typerep/2,
option_typerep/1,
get_constructor_tag/2]).
-export_type([icode/0]). -export_type([icode/0]).
-include("aeso_icode.hrl"). -include("aeso_icode.hrl").
@ -29,12 +40,13 @@
-type icode() :: #{ contract_name => string() -type icode() :: #{ contract_name => string()
, functions => [fun_dec()] , functions => [fun_dec()]
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
, env => [bindings()] , env => [bindings()]
, state_type => aeso_sophia:type() , state_type => aeso_sophia:type()
, event_type => aeso_sophia:type() , event_type => aeso_sophia:type()
, types => #{ type_name() => type_def() } , types => #{ type_name() => type_def() }
, type_vars => #{ string() => aeso_sophia:type() } , type_vars => #{ string() => aeso_sophia:type() }
, constructors => #{ string() => integer() } %% name to tag , constructors => #{ [string()] => integer() } %% name to tag
, options => [any()] , options => [any()]
}. }.
@ -73,10 +85,10 @@ builtin_types() ->
}. }.
builtin_constructors() -> builtin_constructors() ->
#{ "RelativeTTL" => 0 #{ ["RelativeTTL"] => 0
, "FixedTTL" => 1 , ["FixedTTL"] => 1
, "None" => 0 , ["None"] => 0
, "Some" => 1 }. , ["Some"] => 1 }.
map_typerep(K, V) -> map_typerep(K, V) ->
{map, K, V}. {map, K, V}.
@ -91,11 +103,30 @@ new_env() ->
set_name(Name, Icode) -> set_name(Name, Icode) ->
maps:put(contract_name, Name, Icode). maps:put(contract_name, Name, Icode).
-spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode().
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
-spec enter_namespace(aeso_syntax:con(), icode()) -> icode().
enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
Icode#{ namespace => aeso_syntax:qualify(NS1, NS) };
enter_namespace(NS, Icode) ->
Icode#{ namespace => NS }.
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
get_namespace(Icode) -> maps:get(namespace, Icode, false).
-spec qualify(aeso_syntax:id() | aeso_syntax:con(), icode()) -> aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
qualify(X, Icode) ->
case get_namespace(Icode) of
false -> X;
NS -> aeso_syntax:qualify(NS, X)
end.
-spec set_functions([fun_dec()], icode()) -> icode(). -spec set_functions([fun_dec()], icode()) -> icode().
set_functions(NewFuns, Icode) -> set_functions(NewFuns, Icode) ->
maps:put(functions, NewFuns, Icode). maps:put(functions, NewFuns, Icode).
-spec get_constructor_tag(string(), icode()) -> integer(). -spec get_constructor_tag([string()], icode()) -> integer().
get_constructor_tag(Name, #{constructors := Constructors}) -> get_constructor_tag(Name, #{constructors := Constructors}) ->
case maps:get(Name, Constructors, undefined) of case maps:get(Name, Constructors, undefined) of
undefined -> error({undefined_constructor, Name}); undefined -> error({undefined_constructor, Name});

View File

@ -17,7 +17,7 @@
i(Code) -> aeb_opcodes:mnemonic(Code). i(Code) -> aeb_opcodes:mnemonic(Code).
%% We don't track purity or statefulness in the type checker yet. %% We don't track purity or statefulness in the type checker yet.
is_stateful({FName, _, _, _, _}) -> FName /= "init". is_stateful({FName, _, _, _, _}) -> lists:last(FName) /= "init".
is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs). is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs).
@ -105,7 +105,7 @@ make_args(Args) ->
fun_hash({FName, _, Args, _, TypeRep}) -> fun_hash({FName, _, Args, _, TypeRep}) ->
ArgType = {tuple, [T || {_, T} <- Args]}, ArgType = {tuple, [T || {_, T} <- Args]},
<<Hash:256>> = aeso_abi:function_type_hash(list_to_binary(FName), ArgType, TypeRep), <<Hash:256>> = aeso_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
{integer, Hash}. {integer, Hash}.
%% Expects two return addresses below N elements on the stack. Picks the top %% Expects two return addresses below N elements on the stack. Picks the top

View File

@ -36,7 +36,8 @@ decl() ->
?LAZY_P( ?LAZY_P(
choice( choice(
%% 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})
%% 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, []})

View File

@ -147,6 +147,8 @@ decl(D, Options) ->
-spec decl(aeso_syntax:decl()) -> doc(). -spec decl(aeso_syntax:decl()) -> doc().
decl({contract, _, C, Ds}) -> decl({contract, _, C, Ds}) ->
block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds)); block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds));
decl({namespace, _, C, Ds}) ->
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars); decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars);
decl({type_def, _, T, Vars, Def}) -> decl({type_def, _, T, Vars, Def}) ->
Kind = element(1, Def), Kind = element(1, Def),

View File

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

View File

@ -8,13 +8,13 @@
-module(aeso_syntax). -module(aeso_syntax).
-export([get_ann/1, get_ann/2, get_ann/3, set_ann/2]). -export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]).
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]). -export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]). -export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
-export_type([bin_op/0, un_op/0]). -export_type([bin_op/0, un_op/0]).
-export_type([decl/0, letbind/0, typedef/0]). -export_type([decl/0, letbind/0, typedef/0]).
-export_type([arg/0, field_t/0, constructor_t/0]). -export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, pat/0]). -export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, pat/0]).
-export_type([ast/0]). -export_type([ast/0]).
@ -35,6 +35,7 @@
-type tvar() :: {tvar, ann(), name()}. -type tvar() :: {tvar, ann(), name()}.
-type decl() :: {contract, ann(), con(), [decl()]} -type decl() :: {contract, ann(), con(), [decl()]}
| {namespace, ann(), con(), [decl()]}
| {type_decl, ann(), id(), [tvar()]} | {type_decl, ann(), id(), [tvar()]}
| {type_def, ann(), id(), [tvar()], typedef()} | {type_def, ann(), id(), [tvar()], typedef()}
| {fun_decl, ann(), id(), type()} | {fun_decl, ann(), id(), type()}
@ -140,3 +141,8 @@ get_ann(Key, Node) ->
get_ann(Key, Node, Default) -> get_ann(Key, Node, Default) ->
proplists:get_value(Key, get_ann(Node), Default). proplists:get_value(Key, get_ann(Node), Default).
qualify({con, Ann, N}, X) -> qualify({qcon, Ann, [N]}, X);
qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]};
qualify({qcon, _, NS}, {id, Ann, X}) -> {qid, Ann, NS ++ [X]}.

View File

@ -66,7 +66,7 @@ encode_decode_sophia_test() ->
encode_decode_sophia_string(SophiaType, String) -> encode_decode_sophia_string(SophiaType, String) ->
io:format("String ~p~n", [String]), io:format("String ~p~n", [String]),
Code = [ "contract Call =\n" Code = [ "contract MakeCall =\n"
, " function foo : ", SophiaType, " => _\n" , " function foo : ", SophiaType, " => _\n"
, " function __call() = foo(", String, ")\n" ], , " function __call() = foo(", String, ")\n" ],
{ok, _, {Types, _}, Args} = aeso_compiler:check_call(lists:flatten(Code), []), {ok, _, {Types, _}, Args} = aeso_compiler:check_call(lists:flatten(Code), []),

View File

@ -150,11 +150,10 @@ failing_contracts() ->
<<"Ambiguous record type with field y (at line 13, column 25) could be one of\n" <<"Ambiguous record type with field y (at line 13, column 25) could be one of\n"
" - r (at line 4, column 10)\n" " - r (at line 4, column 10)\n"
" - r' (at line 5, column 10)">>, " - r' (at line 5, column 10)">>,
<<"Record type r2 does not have field y (at line 15, column 22)">>,
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
<<"Repeated name x in pattern\n" <<"Repeated name x in pattern\n"
" x :: x (at line 26, column 7)">>, " x :: x (at line 26, column 7)">>,
<<"No record type with fields y, z (at line 14, column 22)">>]} <<"No record type with fields y, z (at line 14, column 22)">>,
<<"No record type with fields y, w (at line 15, column 22)">>]}
, {"init_type_error", , {"init_type_error",
[<<"Cannot unify string\n" [<<"Cannot unify string\n"
" and map(int, int)\n" " and map(int, int)\n"
@ -166,5 +165,7 @@ failing_contracts() ->
, {"missing_fields_in_record_expression", , {"missing_fields_in_record_expression",
[<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>, [<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>,
<<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>, <<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>,
<<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]} <<"The fields y, z are missing when constructing an element of type r('a) (at line 6, column 40)">>]}
, {"namespace_clash",
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]}
]. ].

View File

@ -1,6 +1,6 @@
// Test more advanced chain interactions // Test more advanced chain interactions
contract Chain = contract ChainTest =
record state = { last_bf : address } record state = { last_bf : address }

View File

@ -0,0 +1,5 @@
// You can't shadow existing contracts or namespaces.
contract Call =
function whatever() = ()

View File

@ -0,0 +1,31 @@
namespace Lib =
// namespace Internal =
// function rev(xs, ys) =
// switch(xs)
// [] => ys
// x :: xs => rev(xs, x :: ys)
private
function rev(xs, ys) =
switch(xs)
[] => ys
x :: xs => rev(xs, x :: ys)
function reverse(xs : list('a)) : list('a) = rev(xs, [])
function eqlist(xs : list(int), ys : list(int)) =
switch((xs, ys))
([], []) => true
(x :: xs, y :: ys) => x == y && eqlist(xs, ys)
_ => false
contract TestNamespaces =
record state = { x : int }
function init() = { x = 0 }
function palindrome(xs : list(int)) : bool =
Lib.eqlist(xs, Lib.reverse(xs))