% @doc % type for an SFC AST % % semantically: % #pr{} means the parser succeeded % noparse means the parser didn't consume any tokens % {error, _} means the parser can prove the user made an % error (e.g. unterminated open paren) % % based on so_syntax.erl % % this is the second layer in the parsing pipeline after tokens % % quoth `sophia/docs/so_syntax.md`: % % ## Declarations % % A Sophia file consists of a sequence of *declarations* in a layout block. % % ```c % File ::= Block(TopDecl) % % TopDecl ::= ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl) % | 'contract' 'interface' Con [Implement] '=' Block(Decl) % | 'namespace' Con '=' Block(Decl) % | '@compiler' PragmaOp Version % | 'include' String % | Using % % Implement ::= ':' Sep1(Con, ',') % % Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias % | 'record' Id ['(' TVar* ')'] '=' RecordType % | 'datatype' Id ['(' TVar* ')'] '=' DataType % | 'let' Id [':' Type] '=' Expr % | (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl) % | Using % % FunDecl ::= Id ':' Type // Type signature % | Id Args [':' Type] '=' Block(Stmt) // Definition % | Id Args [':' Type] Block(GuardedDef) // Guarded definitions % % GuardedDef ::= '|' Sep1(Expr, ',') '=' Block(Stmt) % % Using ::= 'using' Con ['as' Con] [UsingParts] % UsingParts ::= 'for' '[' Sep1(Id, ',') ']' % | 'hiding' '[' Sep1(Id, ',') ']' % % PragmaOp ::= '<' | '=<' | '==' | '>=' | '>' % Version ::= Sep1(Int, '.') % % EModifier ::= 'payable' | 'stateful' % FModifier ::= 'stateful' | 'private' % % Args ::= '(' Sep(Pattern, ',') ')' % ``` -module(sfc_ast). -export_type([ ]). -export([ parse/2 ]). -include("sfc.hrl"). %%----------------------------- %% FUNCTIONS %%----------------------------- -spec parse(ParseTarget, SigTokens) -> Result when ParseTarget :: parse_target(), SigTokens :: list(sfc_token()), Result :: perhaps_parsed(). % @doc % parse target against list of tokens; % % for MVP, it is assumed the given list of tokens % contains parse({ast, AstTarget}, Tokens) -> parse_ast(AstTarget, Tokens); parse({tokens, Target}, Tokens) -> parse_tokens(Target, Tokens); parse({prs, Strategy, Target}, Tokens) -> parse_prs(Strategy, Target, Tokens). -spec parse_ast(AstTarget, SigTokens) -> Perhaps when AstTarget :: ast_target(), SigTokens :: [sfc_token()], Perhaps :: perhaps_parsed(). % @private parse where result is an AST % % happy path: returns % % #pr{type = ast, % result = AST :: ast(), % rest = _ :: [sfc_token()]} parse_ast(AstTarget, SrcTokens) -> case AstTarget of file -> parse_ast_file(SrcTokens); top_decl -> parse_ast_top_decl(SrcTokens); _ -> {error, #sfc_err_nyi{}} end. -spec parse_ast_file(SrcTokens) -> Perhaps when SrcTokens :: [sfc_token()], Perhaps :: perhaps_parsed(). % @private parse an #ast_file{} record % % on success returns % #pr{type = ast, % result = #ast_file{}, % rest = []} parse_ast_file([]) -> noparse; %{error, #sfc_err_empty_file{}}; parse_ast_file(SrcTks0) -> case parse({prs, plus, {ast, top_decl}}, SrcTks0) of #pr{type=prs, result=TopDeclPrs, rest=SrcTks1} -> TopDeclAsts = [Ast || #pr{type=ast, result=Ast} <- TopDeclPrs], AstFile = #ast_file{top_decls = TopDeclAsts}, ParseResult = #pr{type = ast, result = AstFile, rest = SrcTks1}, ParseResult; Nope -> Nope end. -spec parse_ast_top_decl(SrcTokens) -> Perhaps when SrcTokens :: [sfc_token()], Perhaps :: perhaps_parsed(). % @private % parse a TopDecl from tokens % % on success returns % % #pr{type = ast, % result = #ast_top_decl{}, % rest = [_ : #sfc_token{}]} % @end parse_ast_top_decl(SrcTks0 = [_ | _]) -> PerhapsTks = parse_tokens(block_item, SrcTks0), case PerhapsTks of #pr{type = tokens, result = []} -> noparse; #pr{type=tokens, result=DeclTks, rest=SrcTks1} -> AstResult = #ast_top_decl{tokens = DeclTks}, ParseResult = #pr{type = ast, result = AstResult, rest = SrcTks1}, ParseResult; _ -> {error, #sfc_err_nyi{}} end; parse_ast_top_decl([]) -> noparse. -spec parse_tokens(TokensTarget, SrcTokens) -> Result when TokensTarget :: tokens_target(), SrcTokens :: [sfc_token()], Result :: perhaps_parsed(). % @private this is for parsing a flat list of tokens % % essentially this is "eat until the next token boundary" % % on success returns % % #pr{type = tokens, % result = [sfc_token()], % rest = [sfc_token()]} % @end % need to think carefully about this case; % for now parsing against an empty list of tokens % is always a success parse_tokens(_, []) -> #pr{type = tokens, result = [], rest = []}; % WMA SrcTokens is nonempty parse_tokens(block, SrcTokens) -> {_, BlkIndent} = start_pos(SrcTokens), TakeToken = fun(Tk) -> BlkIndent =< indent_level(Tk) end, {BlkTokens, Rest} = tw(TakeToken, SrcTokens), #pr{type = tokens, result = BlkTokens, rest = Rest}; parse_tokens(block_item, [H | T]) -> BlkIndent = indent_level(H), TakeToken = fun(Tk) -> BlkIndent < indent_level(Tk) end, {BlockTail, Rest} = tw(TakeToken, T), BlkTokens = [H | BlockTail], #pr{type = tokens, result = BlkTokens, rest = Rest}; parse_tokens(_, _) -> {error, #sfc_err_nyi{}}. % takewhile tw(Pred, List) -> tw(Pred, [], List). tw(Pred, Stk, [X | Xs]) -> case Pred(X) of true -> tw(Pred, [X | Stk], Xs); false -> {lists:reverse(Stk), [X | Xs]} end; tw(_, Stk, []) -> {lists:reverse(Stk), []}. -spec parse_prs(Strategy, Target, SrcTokens) -> Perhaps when Strategy :: prs_strategy(), Target :: parse_target(), SrcTokens :: [sfc_token()], Perhaps :: perhaps_parsed(). % @private on success returns % % #pr{type = prs, % result = [_ :: #pr{}] % rest = [_ :: #sfc_token{}]} parse_prs(Strategy, Target, SrcTokens) -> case Strategy of star -> parse_prs_star(Target, SrcTokens); plus -> parse_prs_plus(Target, SrcTokens); _ -> {error, #sfc_err_nyi{}} end. -spec parse_prs_star(ParseTarget, SrcTokens) -> Perhaps when ParseTarget :: parse_target(), SrcTokens :: [sfc_token()], Perhaps :: perhaps_parsed(). % @private % parse 0 or more of parse_prs_star(_ParseTarget, []) -> #pr{type = prs, result = [], rest = []}; parse_prs_star(ParseTarget, SrcTokens) -> parse_prs_star(ParseTarget, [], SrcTokens). % WMA SrcTokens nonempty parse_prs_star(ParseTarget, Stack, SrcTokens) -> case parse(ParseTarget, SrcTokens) of #pr{result=NewItem, rest=NewSrcTokens} -> NewStack = [NewItem | Stack], parse_prs_star(ParseTarget, NewStack, NewSrcTokens); noparse -> PRs = lists:reverse(Stack), #pr{type = prs, result = PRs, rest = SrcTokens}; Error = {error, _} -> Error end. -spec parse_prs_plus(ParseTarget, SrcTokens) -> Perhaps when ParseTarget :: parse_target(), SrcTokens :: [sfc_token()], Perhaps :: perhaps_parsed(). % @private % parse 1 or more of parse_prs_plus(ParseTgt, SrcTks0) -> case parse(ParseTgt, SrcTks0) of #pr{result = Hd, rest = SrcTks1} -> parse_prs_star(ParseTgt, [Hd], SrcTks1); Nope -> Nope end. -spec start_pos(Tokens) -> Pos when Tokens :: [sfc_token()], Pos :: sfc_pos(). % @private % start position of NONEMPTY list of tokens start_pos([#sfc_token{pos = Result} | _]) -> Result. -spec end_pos(Tokens) -> Pos when Tokens :: [sfc_token()], Pos :: sfc_pos(). % @private % end position of NONEMPTY list of tokens end_pos(Tokens) -> end_pos(Tokens, start_pos(Tokens)). end_pos([#sfc_token{string = S} | Ts], Pos) -> NewPos = sfc_tokenizer:new_pos(Pos, S), end_pos(Ts, NewPos); end_pos([], Result) -> Result. -spec indent_level(Token) -> Col when Token :: sfc_token(), Col :: pos_integer(). % @private % get col val from token indent_level(#sfc_token{pos = {_, C}}) -> C.