Skip to content

Commit 7147b40

Browse files
committed
Create a formal lexer+parser for typespecs and function signatures
1 parent 8d4b281 commit 7147b40

File tree

4 files changed

+118
-0
lines changed

4 files changed

+118
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ erl_crash.dump
1818

1919
# Also ignore archive artifacts (built via "mix archive.build").
2020
*.ez
21+
22+
# Ignore generated code from yecc and leex
23+
src/*_lexer.erl
24+
src/*_parser.erl

lib/abi/parser.ex

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
defmodule ABI.Parser do
2+
@moduledoc false
3+
4+
@doc false
5+
def parse!(str, opts \\ []) do
6+
{:ok, tokens, _} = str |> String.to_charlist |> :ethereum_abi_lexer.string
7+
8+
tokens = case opts[:as] do
9+
nil -> tokens
10+
:type -> [{:"expecting type", 1} | tokens]
11+
:selector -> [{:"expecting selector", 1} | tokens]
12+
end
13+
14+
{:ok, ast} = :ethereum_abi_parser.parse(tokens)
15+
16+
case ast do
17+
{:type, type} -> type
18+
{:selector, selector_parts} -> struct!(ABI.FunctionSelector, selector_parts)
19+
end
20+
end
21+
end

src/ethereum_abi_lexer.xrl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Definitions.
2+
3+
INT = [0-9]+
4+
LETTERS = [a-z_]+
5+
WHITESPACE = [\s\t\n\r]
6+
TYPES = uint|int|address|bool|fixed|uint|ufixed|bytes|function|string
7+
8+
Rules.
9+
10+
{TYPES} : {token, {atom, TokenLine, list_to_atom(TokenChars)}}.
11+
{INT} : {token, {int, TokenLine, list_to_integer(TokenChars)}}.
12+
{LETTERS} : {token, {binary, TokenLine, list_to_binary(TokenChars)}}.
13+
\[ : {token, {'[', TokenLine}}.
14+
\] : {token, {']', TokenLine}}.
15+
\( : {token, {'(', TokenLine}}.
16+
\) : {token, {')', TokenLine}}.
17+
, : {token, {',', TokenLine}}.
18+
-> : {token, {'->', TokenLine}}.
19+
{WHITESPACE}+ : skip_token.
20+
21+
Erlang code.

src/ethereum_abi_parser.yrl

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
Terminals '(' ')' '[' ']' ',' '->' int atom binary 'expecting selector' 'expecting type'.
2+
Nonterminals dispatch selector nontrivial_selector comma_delimited_types type_with_subscripts array_subscripts tuple array_subscript identifier type typespec.
3+
Rootsymbol dispatch.
4+
5+
dispatch -> 'expecting type' type_with_subscripts : {type, '$2'}.
6+
dispatch -> 'expecting selector' selector : {selector, '$2'}.
7+
dispatch -> type_with_subscripts : {selector, #{function => nil, types => ['$1'], returns => nil}}.
8+
dispatch -> nontrivial_selector : {selector, '$1'}.
9+
10+
selector -> typespec : #{function => nil, types => '$1', returns => nil}.
11+
selector -> nontrivial_selector : '$1'.
12+
13+
nontrivial_selector -> typespec '->' type : #{function => nil, types => '$1', returns => '$3'}.
14+
nontrivial_selector -> identifier typespec : #{function => '$1', types => '$2', returns => nil}.
15+
nontrivial_selector -> identifier typespec '->' type : #{function => '$1', types => '$2', returns => '$4'}.
16+
17+
typespec -> '(' ')' : [].
18+
typespec -> '(' comma_delimited_types ')' : '$2'.
19+
20+
tuple -> '(' ')' : {tuple, []}.
21+
tuple -> '(' comma_delimited_types ')' : {tuple, '$2'}.
22+
23+
comma_delimited_types -> type_with_subscripts : ['$1'].
24+
comma_delimited_types -> type_with_subscripts ',' comma_delimited_types : ['$1' | '$3'].
25+
26+
identifier -> atom : atom_to_list(v('$1')).
27+
identifier -> binary : v('$1').
28+
29+
type_with_subscripts -> type : '$1'.
30+
type_with_subscripts -> type array_subscripts : with_subscripts('$1', '$2').
31+
32+
array_subscripts -> array_subscript : ['$1'].
33+
array_subscripts -> array_subscript array_subscripts : ['$1' | '$2'].
34+
35+
array_subscript -> '[' ']' : variable.
36+
array_subscript -> '[' int ']' : v('$2').
37+
38+
type -> atom :
39+
plain_type(v('$1')).
40+
type -> atom int :
41+
juxt_type(v('$1'), v('$2')).
42+
type -> atom int identifier int :
43+
double_juxt_type(v('$1'), v('$4'), v('$2'), v('$3')).
44+
type -> tuple : '$1'.
45+
46+
47+
Erlang code.
48+
49+
v({_Token, _Line, Value}) -> Value.
50+
51+
plain_type(address) -> address;
52+
plain_type(bool) -> bool;
53+
plain_type(function) -> function;
54+
plain_type(string) -> string;
55+
plain_type(bytes) -> bytes;
56+
plain_type(int) -> juxt_type(int, 256);
57+
plain_type(uint) -> juxt_type(uint, 256);
58+
plain_type(fixed) -> double_juxt_type(fixed, "x", 128, 19);
59+
plain_type(ufixed) -> double_juxt_type(ufixed, "x", 128, 19).
60+
61+
with_subscripts(Type, []) -> Type;
62+
with_subscripts(Type, [H | T]) -> with_subscripts(with_subscript(Type, H), T).
63+
64+
with_subscript(Type, variable) -> {array, Type};
65+
with_subscript(Type, N) when is_integer(N), N >= 0 -> {array, Type, N}.
66+
67+
juxt_type(int, M) when M > 0, M =< 256, (M rem 8) =:= 0 -> {int, M};
68+
juxt_type(uint, M) when M > 0, M =< 256, (M rem 8) =:= 0 -> {uint, M};
69+
juxt_type(bytes, M) when M > 0, M =< 32 -> {bytes, M}.
70+
71+
double_juxt_type(fixed, "x", M, N) when M >= 0, M =< 256, (M rem 8) =:= 0, N > 0, N =< 80 -> {fixed, M, N};
72+
double_juxt_type(ufixed, "x", M, N) when M >= 0, M =< 256, (M rem 8) =:= 0, N > 0, N =< 80 -> {ufixed, M, N}.

0 commit comments

Comments
 (0)