Skip to content

Commit d844d84

Browse files
committed
Test yul code blocks in documentation.
1 parent 9a0da17 commit d844d84

File tree

8 files changed

+199
-52
lines changed

8 files changed

+199
-52
lines changed

docs/internals/optimizer.rst

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ compact again at the end.
519519
ExpressionSplitter
520520
^^^^^^^^^^^^^^^^^^
521521

522-
The expression splitter turns expressions like ``add(mload(x), mul(mload(y), 0x20))``
522+
The expression splitter turns expressions like ``add(mload(0x123), mul(mload(0x456), 0x20))``
523523
into a sequence of declarations of unique variables that are assigned sub-expressions
524524
of that expression so that each function call has only variables or literals
525525
as arguments.
@@ -529,9 +529,9 @@ The above would be transformed into
529529
.. code-block:: yul
530530
531531
{
532-
let _1 := mload(y)
532+
let _1 := mload(0x123)
533533
let _2 := mul(_1, 0x20)
534-
let _3 := mload(x)
534+
let _3 := mload(0x456)
535535
let z := add(_3, _2)
536536
}
537537
@@ -633,7 +633,7 @@ The SSA transform converts this snippet to the following:
633633
634634
{
635635
let a_1 := 1
636-
a := a_1
636+
let a := a_1
637637
let a_2 := mload(a_1)
638638
a := a_2
639639
let a_3 := sload(a_2)
@@ -1186,16 +1186,18 @@ The SSA transform rewrites
11861186

11871187
.. code-block:: yul
11881188
1189-
a := E
1189+
let a := calldataload(0)
11901190
mstore(a, 1)
11911191
11921192
to
11931193

11941194
.. code-block:: yul
11951195
1196-
let a_1 := E
1197-
a := a_1
1196+
let a_1 := calldataload(0)
1197+
let a := a_1
11981198
mstore(a_1, 1)
1199+
let a_2 := calldataload(0x20)
1200+
a := a_2
11991201
12001202
The problem is that instead of ``a``, the variable ``a_1`` is used
12011203
whenever ``a`` was referenced. The SSA transform changes statements
@@ -1204,9 +1206,11 @@ snippet is turned into
12041206

12051207
.. code-block:: yul
12061208
1207-
a := E
1209+
let a := calldataload(0)
12081210
let a_1 := a
12091211
mstore(a_1, 1)
1212+
a := calldataload(0x20)
1213+
let a_2 := a
12101214
12111215
This is a very simple equivalence transform, but when we now run the
12121216
Common Subexpression Eliminator, it will replace all occurrences of ``a_1``

docs/yul.rst

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ has to be specified after a colon:
198198

199199
.. code-block:: yul
200200
201-
let x := and("abc":uint32, add(3:uint256, 2:uint256))
201+
// This will not compile (u32 and u256 type not implemented yet)
202+
let x := and("abc":u32, add(3:u256, 2:u256))
202203
203204
204205
Function Calls
@@ -212,10 +213,9 @@ they have to be assigned to local variables.
212213

213214
.. code-block:: yul
214215
216+
function f(x, y) -> a, b { /* ... */ }
215217
mstore(0x80, add(mload(0x80), 3))
216-
// Here, the user-defined function `f` returns
217-
// two values. The definition of the function
218-
// is missing from the example.
218+
// Here, the user-defined function `f` returns two values.
219219
let x, y := f(1, mload(0))
220220
221221
For built-in functions of the EVM, functional expressions
@@ -271,9 +271,10 @@ that returns multiple values.
271271

272272
.. code-block:: yul
273273
274+
// This will not compile (u32 and u256 type not implemented yet)
274275
{
275-
let zero:uint32 := 0:uint32
276-
let v:uint256, t:uint32 := f()
276+
let zero:u32 := 0:u32
277+
let v:u256, t:u32 := f()
277278
let x, y := g()
278279
}
279280
@@ -314,7 +315,7 @@ you need multiple alternatives.
314315

315316
.. code-block:: yul
316317
317-
if eq(value, 0) { revert(0, 0) }
318+
if lt(calldatasize(), 4) { revert(0, 0) }
318319
319320
The curly braces for the body are required.
320321

scripts/common_cmdline.sh

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
# (c) 2016-2019 solidity contributors.
2020
# ------------------------------------------------------------------------------
2121

22+
YULARGS=(--strict-assembly)
2223
FULLARGS=(--optimize --ignore-missing --combined-json "abi,asm,ast,bin,bin-runtime,compact-format,devdoc,hashes,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc")
2324
OLDARGS=(--optimize --combined-json "abi,asm,ast,bin,bin-runtime,devdoc,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc")
2425
function compileFull()
@@ -53,10 +54,18 @@ function compileFull()
5354

5455
local stderr_path; stderr_path=$(mktemp)
5556

57+
if [ "${files: -4}" == ".yul" ]
58+
then
59+
args=("${YULARGS[@]}")
60+
fi
61+
5662
set +e
5763
"$SOLC" "${args[@]}" "${files[@]}" >/dev/null 2>"$stderr_path"
5864
local exit_code=$?
59-
local errors; errors=$(grep -v -E 'Warning: This is a pre-release compiler version|Warning: Experimental features are turned on|pragma experimental ABIEncoderV2|^ +--> |^ +\||^[0-9]+ +\|' < "$stderr_path")
65+
local errors; errors=$(grep -v -E \
66+
-e 'Warning: This is a pre-release compiler version|Warning: Experimental features are turned on|pragma experimental ABIEncoderV2|^ +--> |^ +\||^[0-9]+ +\| ' \
67+
-e 'Warning: Yul is still experimental. Please use the output with care.' < "$stderr_path")
68+
6069
set -e
6170
rm "$stderr_path"
6271

scripts/isolate_tests.py

Lines changed: 69 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
import re
1111
import os
1212
import hashlib
13-
from os.path import join, isfile, split, basename
13+
from os.path import join, isfile, basename
1414
from argparse import ArgumentParser
15+
from textwrap import indent, dedent
1516

1617
def extract_test_cases(path):
1718
with open(path, encoding="utf8", errors='ignore', mode='r', newline='') as file:
@@ -36,11 +37,42 @@ def extract_test_cases(path):
3637

3738
return tests
3839

39-
# Extract code examples based on a start marker
40+
def extract_solidity_docs_cases(path):
41+
tests = extract_docs_cases(path, [".. code-block:: solidity", '::'])
42+
43+
codeStart = "(// SPDX-License-Identifier:|pragma solidity|contract.*{|library.*{|interface.*{)"
44+
45+
# Filter out tests that are not supposed to be compilable.
46+
return [
47+
test.lstrip("\n")
48+
for test in tests
49+
if re.search(r'^\s{4}' + codeStart, test, re.MULTILINE) is not None
50+
]
51+
52+
def extract_yul_docs_cases(path):
53+
tests = extract_docs_cases(path, [".. code-block:: yul"])
54+
55+
def wrap_in_object(code):
56+
for line in code.splitlines():
57+
line = line.lstrip()
58+
if line.startswith("//"):
59+
continue
60+
if not line.startswith("object") and not line.startswith("{"):
61+
return indent("{{\n{}\n}}\n\n".format(code.rstrip()), " ")
62+
break
63+
64+
return code
65+
66+
return [
67+
wrap_in_object(test)
68+
for test in tests
69+
if test.strip() != ""
70+
]
71+
72+
# Extract code examples based on the 'beginMarker' parameter
4073
# up until we reach EOF or a line that is not empty and doesn't start with 4
4174
# spaces.
42-
def extract_docs_cases(path):
43-
beginMarkers = ['.. code-block:: solidity', '::']
75+
def extract_docs_cases(path, beginMarkers):
4476
immediatelyAfterMarker = False
4577
insideBlock = False
4678
tests = []
@@ -59,48 +91,45 @@ def extract_docs_cases(path):
5991
if line == '' or line.startswith(" "):
6092
tests[-1] += line + "\n"
6193
immediatelyAfterMarker = False
62-
else:
63-
insideBlock = False
64-
elif any(map(line.lower().startswith, beginMarkers)):
94+
continue
95+
96+
insideBlock = False
97+
if any(map(line.lower().startswith, beginMarkers)):
6598
insideBlock = True
6699
immediatelyAfterMarker = True
67100
tests += ['']
68101

69-
codeStart = "(// SPDX-License-Identifier:|pragma solidity|contract.*{|library.*{|interface.*{)"
70-
71-
for test in tests:
72-
if re.search(r'^\s{0,3}' + codeStart, test, re.MULTILINE):
73-
print("Indentation error in " + path + ":")
74-
print(test)
75-
exit(1)
76-
77-
# Filter out tests that are not supposed to be compilable.
78-
return [
79-
test.lstrip("\n")
80-
for test in tests
81-
if re.search(r'^\s{4}' + codeStart, test, re.MULTILINE) is not None
82-
]
102+
return tests
83103

84-
def write_cases(f, tests):
104+
def write_cases(f, solidityTests, yulTests):
85105
cleaned_filename = f.replace(".","_").replace("-","_").replace(" ","_").lower()
86-
for test in tests:
106+
for language, test in [("sol", t) for t in solidityTests] + [("yul", t) for t in yulTests]:
87107
# When code examples are extracted they are indented by 8 spaces, which violates the style guide,
88108
# so before checking remove 4 spaces from each line.
89-
remainder = re.sub(r'^ {4}', '', test, 0, re.MULTILINE)
90-
sol_filename = 'test_%s_%s.sol' % (hashlib.sha256(test.encode("utf-8")).hexdigest(), cleaned_filename)
109+
remainder = dedent(test)
110+
sol_filename = 'test_%s_%s.%s' % (hashlib.sha256(test.encode("utf-8")).hexdigest(), cleaned_filename, language)
91111
with open(sol_filename, mode='w', encoding='utf8', newline='') as fi:
92112
fi.write(remainder)
93113

94-
def extract_and_write(path):
114+
def extract_and_write(path, language):
115+
assert language in ["solidity", "yul", ""]
116+
yulCases = []
117+
cases = []
118+
95119
if path.lower().endswith('.rst'):
96-
cases = extract_docs_cases(path)
120+
if language in ("solidity", ""):
121+
cases = extract_solidity_docs_cases(path)
122+
123+
if language in ("yul", ""):
124+
yulCases = extract_yul_docs_cases(path)
97125
elif path.endswith('.sol'):
98-
with open(path, mode='r', encoding='utf8', newline='') as f:
99-
cases = [f.read()]
126+
if language in ("solidity", ""):
127+
with open(path, mode='r', encoding='utf8', newline='') as f:
128+
cases = [f.read()]
100129
else:
101130
cases = extract_test_cases(path)
102131

103-
write_cases(basename(path), cases)
132+
write_cases(basename(path), cases, yulCases)
104133

105134
if __name__ == '__main__':
106135
script_description = (
@@ -110,11 +139,19 @@ def extract_and_write(path):
110139

111140
parser = ArgumentParser(description=script_description)
112141
parser.add_argument(dest='path', help='Path to file or directory to look for code in.')
142+
parser.add_argument(
143+
'-l', '--language',
144+
dest='language',
145+
choices=["yul", "solidity"],
146+
default="",
147+
action='store',
148+
help="Extract only code blocks in the given language"
149+
)
113150
options = parser.parse_args()
114151
path = options.path
115152

116153
if isfile(path):
117-
extract_and_write(path)
154+
extract_and_write(path, options.language)
118155
else:
119156
for root, subdirs, files in os.walk(path):
120157
if '_build' in subdirs:
@@ -125,4 +162,4 @@ def extract_and_write(path):
125162
if basename(f) == "invalid_utf8_sequence.sol":
126163
continue # ignore the test with broken utf-8 encoding
127164
path = join(root, f)
128-
extract_and_write(path)
165+
extract_and_write(path, options.language)

test/cmdlineTests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ SOLTMPDIR=$(mktemp -d)
364364
"$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/docs/
365365
developmentVersion=$("$REPO_ROOT/scripts/get_version.sh")
366366

367-
for f in *.sol
367+
for f in *.yul *.sol
368368
do
369369
# The contributors guide uses syntax tests, but we cannot
370370
# really handle them here.

test/scripts/fixtures/code_block.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,25 @@ Some text
2020
contract C {}
2121

2222
More text.
23+
24+
.. code-block:: yul
25+
26+
let x := add(1, 5)
27+
28+
.. code-block:: yul
29+
30+
// Yul code wrapped in object
31+
{
32+
{
33+
let y := mul(3, 5)
34+
}
35+
}
36+
37+
.. code-block:: yul
38+
// Yul code wrapped in named object
39+
object "Test" {
40+
{
41+
let y := mul(6, 9)
42+
}
43+
}
44+

test/scripts/fixtures/code_block_with_directives.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,26 @@ Sphinx does not complain about these.
4646
contract E {}
4747
4848
More text.
49+
50+
.. code-block:: yul
51+
52+
:force:
53+
let x := add(1, 5)
54+
55+
.. code-block:: yul
56+
57+
:linenos:
58+
:language: Yul
59+
// Yul code wrapped in object
60+
{
61+
let y := mul(3, 5)
62+
}
63+
64+
.. code-block:: yul
65+
66+
// Yul code wrapped in named object
67+
object "Test" {
68+
let y := mul(3, 5)
69+
:linenos:
70+
}
71+

0 commit comments

Comments
 (0)