330 lines
8.7 KiB
Erlang
330 lines
8.7 KiB
Erlang
% @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.
|