import re
from pathlib import Path
from textwrap import dedent
REPO_ROOT = Path(__file__).resolve().parents[1]
OPCODE_SOURCE = REPO_ROOT / "tools" / "OpCode.cs"
LOCAL_OPCODE_SOURCE = REPO_ROOT / "neo_csharp" / "vm" / "src" / "Neo.VM" / "OpCode.cs"
OUTPUT = REPO_ROOT / "src" / "opcodes_generated.rs"
def to_rust_name(name: str) -> str:
special_prefix = {
"PUSHINT": lambda suffix: f"Pushint{suffix}",
"PUSHDATA": lambda suffix: f"Pushdata{suffix}",
"PUSH": lambda suffix: f"Push{suffix.title()}",
"JMPIFNOT": lambda suffix: "Jmpifnot" + suffix.title(),
"JMPIF": lambda suffix: "Jmpif" + suffix.title(),
"JMP": lambda suffix: "Jmp" + suffix.title(),
"CALL": lambda suffix: "Call" + suffix.title(),
"INITSSLOT": lambda suffix: "Initsslot" + suffix.title(),
"INITSLOT": lambda suffix: "Initslot" + suffix.title(),
}
for prefix, builder in special_prefix.items():
if name.startswith(prefix):
return builder(name[len(prefix) :])
temp = name.lower().replace("_", " ")
return temp.title().replace(" ", "")
def operand_encoding(name: str) -> str:
if name.startswith("PUSHINT"):
size = int(name[len("PUSHINT") :])
mapping = {
8: "OperandEncoding::I8",
16: "OperandEncoding::I16",
32: "OperandEncoding::I32",
64: "OperandEncoding::I64",
}
if size in mapping:
return mapping[size]
return f"OperandEncoding::Bytes({size // 8})"
if name == "PUSHA":
return "OperandEncoding::I32"
if name == "PUSHDATA1":
return "OperandEncoding::Data1"
if name == "PUSHDATA2":
return "OperandEncoding::Data2"
if name == "PUSHDATA4":
return "OperandEncoding::Data4"
short_targets = {
"JMP",
"JMPIF",
"JMPIFNOT",
"JMPEQ",
"JMPNE",
"JMPGT",
"JMPGE",
"JMPLT",
"JMPLE",
"CALL",
"ENDTRY",
}
long_targets = {f"{base}_L" for base in short_targets}
if name in short_targets:
return "OperandEncoding::Jump8"
if name in long_targets:
return "OperandEncoding::Jump32"
if name == "CALLT":
return "OperandEncoding::U16"
if name == "TRY":
return "OperandEncoding::Bytes(2)"
if name == "TRY_L":
return "OperandEncoding::Bytes(8)"
if name == "SYSCALL":
return "OperandEncoding::Syscall"
if name in {
"INITSSLOT",
"LDSFLD",
"STSFLD",
"LDLOC",
"STLOC",
"LDARG",
"STARG",
"NEWARRAY_T",
"ISTYPE",
"CONVERT",
}:
return "OperandEncoding::U8"
if name == "INITSLOT":
return "OperandEncoding::Bytes(2)"
return "OperandEncoding::None"
def resolve_opcode_source() -> Path:
if OPCODE_SOURCE.exists():
return OPCODE_SOURCE
if LOCAL_OPCODE_SOURCE.exists():
return LOCAL_OPCODE_SOURCE
return OPCODE_SOURCE
def parse_opcodes(source: Path) -> list[tuple[str, int]]:
pattern = re.compile(
r"(?:\[OperandSize\([^\]]*\)\]\s*)?(\w+)\s*=\s*0x([0-9A-Fa-f]{2})",
re.MULTILINE,
)
matches = pattern.findall(source.read_text())
return [(name, int(value, 16)) for name, value in matches]
def main() -> None:
source = resolve_opcode_source()
entries = parse_opcodes(source)
variants = []
from_byte = []
to_byte = []
mnemonics = []
encodings = []
for name, value in entries:
rust_name = to_rust_name(name)
variants.append(f" {rust_name},")
from_byte.append(f" 0x{value:02X} => OpCode::{rust_name},")
to_byte.append(f" OpCode::{rust_name} => 0x{value:02X},")
mnemonics.append(f" OpCode::{rust_name} => \"{name}\",")
encodings.append(
f" OpCode::{rust_name} => {operand_encoding(name)},"
)
template = dedent(
"""\
// This file is @generated by tools/generate_opcodes.py. Do not edit manually.
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OpCode {{
{variants}
Unknown(u8),
}}
impl OpCode {{
pub fn from_byte(byte: u8) -> Self {{
match byte {{
{from_byte}
other => OpCode::Unknown(other),
}}
}}
pub fn byte(self) -> u8 {{
match self {{
{to_byte}
OpCode::Unknown(value) => value,
}}
}}
pub fn mnemonic(self) -> &'static str {{
match self {{
{mnemonics}
OpCode::Unknown(_) => "UNKNOWN",
}}
}}
pub fn operand_encoding(self) -> OperandEncoding {{
match self {{
{encodings}
OpCode::Unknown(_) => OperandEncoding::None,
}}
}}
}}
"""
)
generated = template.format(
variants="\n".join(variants),
from_byte="\n".join(from_byte),
to_byte="\n".join(to_byte),
mnemonics="\n".join(mnemonics),
encodings="\n".join(encodings),
)
OUTPUT.write_text(generated)
print(f"wrote {OUTPUT}")
if __name__ == "__main__":
source = resolve_opcode_source()
if not source.exists():
raise SystemExit(
f"missing opcode source. Looked for {OPCODE_SOURCE} and {LOCAL_OPCODE_SOURCE}."
)
main()