Skip to content

Commit

Permalink
Add support for evm opcode documentation (#4319)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcosmartinezfco committed Nov 21, 2022
1 parent 03d6975 commit fcb7683
Show file tree
Hide file tree
Showing 4 changed files with 1,198 additions and 0 deletions.
147 changes: 147 additions & 0 deletions etc/scripts/docenizers/docenizer-evm.py
@@ -0,0 +1,147 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import json
import os
import sys
import urllib
import re
from urllib import request
from urllib import parse

parser = argparse.ArgumentParser(description='Docenizes the EVM documentation')
parser.add_argument('-i', '--inputfolder', type=str,
help='Folder where the input files reside as .html. Default is ./evm-inst-docs/',
default='evm-inst-docs')
parser.add_argument('-o', '--outputpath', type=str, help='Final path of the .js file. Default is ./evm-inst-docs.js',
default='./evm-inst-docs.js')
parser.add_argument('-d', '--downloadfolder', type=str,
help='Folder where the archive will be downloaded and extracted', default='evm-inst-docs')

# | `0x00` | STOP | Halts execution | - | 0 |
MNEMONIC_RE = re.compile('^\| `0x([A-Za-z0-9]+)` \| (.*) \| .* \| .* \| .* \|$')

# Where to extract the asmdoc archive.
ARCHIVE_DESC_URL = "https://raw.githubusercontent.com/comitylabs/evm.codes/main/opcodes.json"
ARCHIVE_DESC_NAME = "opcodes.json"
ARCHIVE_MNEM_URL = "https://raw.githubusercontent.com/crytic/evm-opcodes/master/README.md"
ARCHIVE_MNEM_NAME = "README.md"


class Instruction(object):
def __init__(self, opcode, mnemonic, tooltip, body):
self.opcode = opcode
self.mnemonic = mnemonic
self.tooltip = tooltip.rstrip(': ,')
self.body = body

def __str__(self):
return f"{self.opcode} = {self.tooltip}\n{self.body}"


def get_url_for_instruction(instr):
return f"https://www.evm.codes/#{urllib.parse.quote(instr.opcode)}"


def download_asm_doc_archive(downloadfolder):
if not os.path.exists(downloadfolder):
print(f"Creating {downloadfolder} as download folder")
os.makedirs(downloadfolder)
elif not os.path.isdir(downloadfolder):
print(f"Error: download folder {downloadfolder} is not a directory")
sys.exit(1)
archive_desc_name = os.path.join(downloadfolder, ARCHIVE_DESC_NAME)
print("Downloading archive...")
urllib.request.urlretrieve(ARCHIVE_DESC_URL, archive_desc_name)

archive_mnem_name = os.path.join(downloadfolder, ARCHIVE_MNEM_NAME)
print("Downloading archive...")
urllib.request.urlretrieve(ARCHIVE_MNEM_URL, archive_mnem_name)


def get_description_paragraphs(opcode):
stack_input = 'Input: ' + (f'<code>{opcode["input"]}</code>' if opcode["input"] != "" else '-')
stack_output = 'Output: ' + (f'<code>{opcode["output"]}</code>' if opcode["output"] != "" else '-')
return [opcode["description"], stack_input, stack_output]


def generate_opcode_mnemonic_map(mnemonic_file):
mnemonic_map = {}
for line in mnemonic_file:
match = MNEMONIC_RE.match(line)
if match:
mnemonic_map[match.group(1)] = match.group(2)
return mnemonic_map


def is_valid_opcode(opcode, mnemonic_map):
return opcode in mnemonic_map

def parse(descriptions_file, mnemonic_file):
descriptions = json.load(descriptions_file)
mnemonic_map = generate_opcode_mnemonic_map(mnemonic_file)
print(mnemonic_map)
opcodes = descriptions.items()
instructions = []
for opcode, body in opcodes:
if is_valid_opcode(opcode, mnemonic_map):
mnemonic = mnemonic_map[opcode]
opcode_desc = get_description_paragraphs(body)
instructions.append(Instruction(
opcode,
mnemonic,
opcode_desc[0],
'\n'.join(opcode_desc))
)
return instructions


def parse_html(directory):
print("Parsing instructions...")
instructions = []
try:
with open(os.path.join(directory, ARCHIVE_DESC_NAME), encoding='utf-8') as description_file:
with open(os.path.join(directory, ARCHIVE_MNEM_NAME), encoding='utf-8') as mnemonic_file:
instructions = parse(description_file, mnemonic_file)
except Exception as e:
print(f"Error parsing files:\n{e}")

return instructions


def main():
args = parser.parse_args()
print(f"Called with: {args}")
# If we don't have the html folder already...
if not os.path.isdir(os.path.join(args.inputfolder, 'html')):
try:
download_asm_doc_archive(args.downloadfolder)
except IOError as e:
print("Error when downloading archive:")
print(e)
sys.exit(1)
instructions = parse_html(args.inputfolder)
instructions.sort(key=lambda b: b.opcode)
all_inst = set()
print(f"Writing {len(instructions)} instructions")
with open(args.outputpath, 'w') as f:
f.write("""
export function getAsmOpcode(opcode) {
if (!opcode) return;
switch (opcode.toUpperCase()) {
""")
for inst in instructions:
f.write(f' case "{inst.mnemonic}":\n')
f.write(' return {}'.format(json.dumps({
"tooltip": inst.tooltip,
"html": inst.body,
"url": get_url_for_instruction(inst)
}, indent=16, separators=(',', ': '), sort_keys=True))[:-1] + ' };\n\n')
f.write("""
}
}
""")


if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions lib/asm-docs/_all.ts
Expand Up @@ -25,6 +25,7 @@
export {Amd64DocumentationProvider} from './amd64';
export {Arm32DocumentationProvider} from './arm32';
export {AvrDocumentationProvider} from './avr';
export {EvmDocumentationProvider} from './evm';
export {JavaDocumentationProvider} from './java';
export {LLVMDocumentationProvider} from './llvm';
export {Mos6502DocumentationProvider} from './mos6502';
Expand Down
35 changes: 35 additions & 0 deletions lib/asm-docs/evm.ts
@@ -0,0 +1,35 @@
// Copyright (c) 2022, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

import {AssemblyInstructionInfo, BaseAssemblyDocumentationProvider} from './base';
import {getAsmOpcode} from './generated/asm-docs-evm';

export class EvmDocumentationProvider extends BaseAssemblyDocumentationProvider {
public static get key() {
return 'evm';
}
public override getInstructionInformation(instruction: string): AssemblyInstructionInfo | null {
return getAsmOpcode(instruction) || null;
}
}

0 comments on commit fcb7683

Please sign in to comment.