% @doc % File ::= Block(TopDecl) -record(ast_file, {top_decls = none :: none | [top_decl()]}). -type ast() :: #ast_file{} | top_decl() | #ast_nyi{} . %% Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias %% | 'record' Id ['(' TVar* ')'] '=' RecordType %% | 'datatype' Id ['(' TVar* ')'] '=' DataType %% | 'let' Id [':' Type] '=' Expr %% | (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl) %% | Using %-record(ast_type_alias, % {name = none :: none | string(), % tvars = none :: none | [string()], % rewrites_to = none :: none | ast_type()}). -type gulp_target() :: ast_file | top_decl | ast_ct | ast_nyi . % gulp means it must consume all input -spec gulp(AstTarget, SigTokens) -> Perhaps when AstTarget :: gulp_target(), SigTokens :: [sfc_token()], Perhaps :: {gulp, ast()} | {error, sfc_err()}. gulp(ast_file, Tokens) -> gulp_file(Tokens); gulp(top_decl, Tokens) -> Targets = [ast_ct, ast_nyi], gulp_oneof(Targets, Tokens); gulp(ast_ct, Tokens) -> gulp_ct(#ast_ct{}, Tokens); gulp(ast_nyi, Tokens) -> {gulp, #ast_nyi{tokens = Tokens}}; gulp({block_of, X}, Tokens) -> {barf, ItemChunks, []} = sfc_token_chunks:barf(block_as_items, Tokens), gulp_block_of(X, ItemChunks); gulp(Nyi, Tokens) -> Msg = io_lib:format("sfc_ast:gulp/2: unknown target: ~p", [Nyi]), Err = #sfc_err{atom = gulp_nyi, string = Msg, extra = [{target, Nyi}, {tokens, Tokens}]}, {error, Err}. % FIXME: payable and main need to be in that order i think gulp_ct(Ast = #ast_ct{payable = none}, Tokens) -> case Tokens of [#sfc_token{string = "payable", type = kwd} | NewTokens] -> gulp_ct(Ast#ast_ct{payable = payable}, NewTokens); _ -> gulp_ct(Ast#ast_ct{payable = false}, Tokens) end; gulp_ct(Ast = #ast_ct{main = none}, Tokens) -> case Tokens of [#sfc_token{string = "main", type = kwd} | NewTokens] -> gulp_ct(Ast#ast_ct{main = main}, NewTokens); _ -> gulp_ct(Ast#ast_ct{main = false}, Tokens) end; gulp_ct(Ast = #ast_ct{contract = none}, Tokens) -> case Tokens of [#sfc_token{string = "contract", type = kwd} | NewTokens] -> gulp_ct(Ast#ast_ct{contract = contract}, NewTokens); % FIXME: reject logic applies to choice of branch, therefore % should be contained in branchpoint code _ -> reject %[#sfc_token{pos = P, string = S} | _] -> % {error, #sfc_err{atom = no_kwd_contract, % extra = [{pos, P}, % {expecting, "contract"}, % {got, S}, % {ast, Ast}, % {tokens, Tokens}]}}; %[] -> % {error, #sfc_err{atom = no_kwd_contract, % extra = [{pos, none}, % {expecting, "contract"}, % {got, eof}, % {ast, Ast}, % {tokens, Tokens}]}} end; gulp_ct(Ast = #ast_ct{name = none}, Tokens) -> case Tokens of [#sfc_token{string = Name, type = con} | NewTokens] -> gulp_ct(Ast#ast_ct{name = Name}, NewTokens); _ -> reject end; gulp_ct(Ast = #ast_ct{implements = none}, Tokens) -> case slurp_ct_impls(Tokens) of {slurp, Names, NewTokens} -> gulp_ct(Ast#ast_ct{implements = {':', Names}}, NewTokens); reject -> gulp_ct(Ast#ast_ct{implements = {':', []}}, Tokens); Poison -> Poison end; gulp_ct(Ast = #ast_ct{eq = none}, Tokens) -> case Tokens of [#sfc_token{string = "=", type = op} | NewTokens] -> gulp_ct(Ast#ast_ct{eq = '='}, NewTokens); _ -> {error, #sfc_err{atom = no_eq}} end; gulp_ct(Ast = #ast_ct{decls = none}, Tokens) -> Decls = [gulp(decl, Item) || Item <- sfc_token_chunks:unsafe_block_to_items(Tokens)], {gulp, Ast#ast_ct{decls = Decls}}; gulp_ct(_, _) -> reject. slurp_ct_impls([#sfc_token{string = ":", type = op}, #sfc_token{string = Con1, type = con} | Rest]) -> slurp_ct_impls2(Rest, [Con1]); slurp_ct_impls(_) -> reject. slurp_ct_impls2([#sfc_token{string = ",", type = punct}, #sfc_token{string = Con1, type = con} | Rest], Acc) -> slurp_ct_impls2(Rest, [Con1 | Acc]); slurp_ct_impls2(Rest, Names) -> {slurp, lists:reverse(Names), Rest}. -spec gulp_file(SigTokens) -> Perhaps when SigTokens :: [sfc_token()], Perhaps :: {gulp, #ast_file{}} | {error, sfc_err()}. % @private % `file` enforces that the entire SigTokens is one % block, chokes otherwise gulp_file([]) -> {error, #sfc_err{atom = empty_file}}; gulp_file(FileTokens = [#sfc_token{pos = FilePos} | _]) -> case sfc_token_chunks:barf(block, FileTokens) of % happy path: got the whole file back {barf, FileTokens, []} -> gulp_full_file(FileTokens); % sad path: block terminated {barf, _, [#sfc_token{pos = EndPos}]} -> Msg = io_lib:format("block starting at ~p ends at ~p instead of EOF", [FilePos, EndPos]), {error, #sfc_err{atom = bad_file, string = Msg}}; Nyi -> {error, #sfc_err{atom = bad_file_nyi, extra = Nyi}} end. % FIXME: need to rethink types here in order to handle syntax errors % from different blocks independently. % file = block(top_decl) gulp_full_file(BlockTokens) -> ItemChunks = sfc_token_chunks:unsafe_block_to_items(BlockTokens), gulp_file_decls([], [], ItemChunks). gulp_file_decls(Decls, Errs, [DeclTokens | Rest]) -> case gulp(top_decl, DeclTokens) of {gulp, NewDecl} -> gulp_file_decls([NewDecl | Decls], Errs, Rest); reject -> ErrPos = sfc_token_chunks:start_pos(DeclTokens), NewErr = #sfc_err{atom = bad_top_decl, extra = [{tokens, DeclTokens}, {pos, ErrPos}]}, gulp_file_decls(Decls, [NewErr | Errs], Rest); Poison -> gulp_file_decls(Decls, [Poison | Errs], Rest) end; % end of block gulp_file_decls(Decls, _Errs = [], _Input = []) -> {gulp, #ast_file{top_decls = lists:reverse(Decls)}}; gulp_file_decls(_Decls, Errs, _Input = []) -> {error, #sfc_err{atom = many, extra = Errs}}.