From ff7158314224ba8c98ee81ce35f798baac576e80 Mon Sep 17 00:00:00 2001 From: Adrian Tiberius Date: Wed, 6 May 2015 19:26:56 +0200 Subject: [PATCH] Readded serpent. Reenabled antlr. --- ethereumj-core/build.gradle | 40 +- .../org/ethereum/serpent/ParserUtils.java | 66 ++ .../org/ethereum/serpent/SerpentCompiler.java | 227 +++++ .../serpent/SerpentToAssemblyCompiler.java | 778 ++++++++++++++++++ 4 files changed, 1104 insertions(+), 7 deletions(-) create mode 100644 ethereumj-core/src/main/java/org/ethereum/serpent/ParserUtils.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/serpent/SerpentCompiler.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/serpent/SerpentToAssemblyCompiler.java diff --git a/ethereumj-core/build.gradle b/ethereumj-core/build.gradle index 7d795c7c..83eee38d 100644 --- a/ethereumj-core/build.gradle +++ b/ethereumj-core/build.gradle @@ -1,3 +1,17 @@ +buildscript { + repositories { + maven { + name 'JFrog OSS snapshot repo' + url 'https://oss.jfrog.org/oss-snapshot-local/' + } + jcenter() + } + dependencies { + classpath 'me.champeau.gradle:antlr4-gradle-plugin:0.1' + } +} + +apply plugin: 'me.champeau.gradle.antlr4' apply plugin: 'com.android.library' repositories { @@ -10,6 +24,9 @@ repositories { } } +ext.generatedSrcDir = file('src/gen/java') + + android { compileSdkVersion 21 buildToolsVersion "21.1.2" @@ -27,17 +44,26 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + sourceSets { + main { + java { + srcDirs += generatedSrcDir + } + } + } } -//ext.generatedSrcDir = file('src/gen/java') -//sourceSets.main.java.srcDirs += generatedSrcDir -//antlr4 { -// extraArgs = ['-package', 'org.ethereum.serpent'] -// output = file("${generatedSrcDir}/org/ethereum/serpent") -//} +antlr4 { + extraArgs = ['-package', 'org.ethereum.serpent'] + output = file("${generatedSrcDir}/org/ethereum/serpent") +} + //compileJava.dependsOn antlr4 +configurations { + compile.extendsFrom antlr4 +} ext { slf4jVersion = '1.7.7' @@ -92,7 +118,7 @@ dependencies { // for serpent compilation - //compile 'com.yuvalshavit:antlr-denter:1.1' + compile 'com.yuvalshavit:antlr-denter:1.1' //compile 'org.javassist:javassist:3.15.0-GA' compile 'org.slf4j:slf4j-android:1.6.1-RC1' diff --git a/ethereumj-core/src/main/java/org/ethereum/serpent/ParserUtils.java b/ethereumj-core/src/main/java/org/ethereum/serpent/ParserUtils.java new file mode 100644 index 00000000..0b6a6ead --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/serpent/ParserUtils.java @@ -0,0 +1,66 @@ +package org.ethereum.serpent; + +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.DiagnosticErrorListener; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.TokenStream; +import org.antlr.v4.runtime.atn.PredictionMode; +import org.antlr.v4.runtime.misc.Nullable; + +public class ParserUtils { + private ParserUtils() { + } + + public static L getLexer(Class lexerClass, String source) { + CharStream input = new ANTLRInputStream(source); + L lexer; + try { + lexer = lexerClass.getConstructor(CharStream.class).newInstance(input); + } catch (Exception e) { + throw new IllegalArgumentException("couldn't invoke lexer constructor", e); + } + lexer.addErrorListener(new AntlrFailureListener()); + return lexer; + } + + public static

P getParser(Class lexerClass, Class

parserClass, String + source) { + Lexer lexer = getLexer(lexerClass, source); + TokenStream tokens = new CommonTokenStream(lexer); + + P parser; + try { + parser = parserClass.getConstructor(TokenStream.class).newInstance(tokens); + } catch (Exception e) { + throw new IllegalArgumentException("couldn't invoke parser constructor", e); + } + parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION); + parser.removeErrorListeners(); // don't spit to stderr + parser.addErrorListener(new DiagnosticErrorListener()); + parser.addErrorListener(new AntlrFailureListener()); + + return parser; + } + + private static class AntlrFailureListener extends BaseErrorListener { + @Override + public void syntaxError(Recognizer recognizer, @Nullable Object offendingSymbol, int line, + int charPositionInLine, String msg, @Nullable RecognitionException e) { + throw new AntlrParseException(line, charPositionInLine, msg, e); + } + } + + public static class AntlrParseException extends RuntimeException { + public AntlrParseException(int line, int posInLine, String msg, Throwable cause) { + // posInLine comes in 0-indexed, but we want to 1-index it so it lines up with what editors say (they + // tend to 1-index) + super(String.format("at line %d column %d: %s", line, posInLine + 1, msg), cause); + } + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/serpent/SerpentCompiler.java b/ethereumj-core/src/main/java/org/ethereum/serpent/SerpentCompiler.java new file mode 100644 index 00000000..8f94cf55 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/serpent/SerpentCompiler.java @@ -0,0 +1,227 @@ +package org.ethereum.serpent; + +import org.ethereum.util.ByteUtil; +import org.ethereum.vm.OpCode; + +import org.antlr.v4.runtime.tree.ParseTree; + +import org.spongycastle.util.Arrays; +import org.spongycastle.util.BigIntegers; + +import java.io.ByteArrayOutputStream; + +import java.math.BigInteger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Roman Mandeleil + * @since 13.05.14 + */ +public class SerpentCompiler { + + public static String compile(String code) { + SerpentParser parser = ParserUtils.getParser(SerpentLexer.class, SerpentParser.class, + code); + ParseTree tree = parser.parse(); + + String result = new SerpentToAssemblyCompiler().visit(tree); + result = result.replaceAll("\\s+", " "); + result = result.trim(); + + return result; + } + + public static String compileFullNotion(String code) { + SerpentParser parser = ParserUtils.getParser(SerpentLexer.class, + SerpentParser.class, code); + ParseTree tree = parser.parse_init_code_block(); + + String result = new SerpentToAssemblyCompiler().visit(tree); + result = result.replaceAll("\\s+", " "); + result = result.trim(); + return result; + } + + public static byte[] compileFullNotionAssemblyToMachine(String code) { + byte[] initCode = compileAssemblyToMachine(extractInitBlock(code)); + byte[] codeCode = compileAssemblyToMachine(extractCodeBlock(code)); + return encodeMachineCodeForVMRun(codeCode, initCode); + } + + public static String extractInitBlock(String code) { + String result = ""; + Pattern pattern = Pattern.compile("\\[init (.*?) init\\]"); + Matcher matcher = pattern.matcher(code); + if (matcher.find()) { + result = matcher.group(1); + } + return result; + } + + public static String extractCodeBlock(String code) { + String result = ""; + Pattern pattern = Pattern.compile("\\[code (.*?) code\\]"); + Matcher matcher = pattern.matcher(code); + if (matcher.find()) { + result = matcher.group(1); + } + return result; + } + + public static byte[] compileAssemblyToMachine(String code) { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + String[] lexaArr = code.split("\\s+"); + + List lexaList = new ArrayList<>(); + Collections.addAll(lexaList, lexaArr); + + // Encode push_n numbers + boolean skipping = false; + for (int i = 0; i < lexaList.size(); ++i) { + + String lexa = lexaList.get(i); + + { // skipping the [asm asm] block + if (lexa.equals("asm]")) { + skipping = false; + lexaList.remove(i); + --i; + continue; + } + if (lexa.equals("[asm")) { + skipping = true; + lexaList.remove(i); + --i; + continue; + } + if (skipping) + continue; + } + + if (OpCode.contains(lexa) || + lexa.contains("REF_") || + lexa.contains("LABEL_")) continue; + + int bytesNum = ByteUtil.numBytes(lexa); + + String num = lexaList.remove(i); + BigInteger bNum = new BigInteger(num); + byte[] bytes = BigIntegers.asUnsignedByteArray(bNum); + if (bytes.length == 0) bytes = new byte[]{0}; + + for (int j = bytes.length; j > 0; --j) { + lexaList.add(i, (bytes[j - 1] & 0xFF) + ""); + } + lexaList.add(i, "PUSH" + bytesNum); + i = i + bytesNum; + } + + // encode ref for 5 bytes + for (int i = 0; i < lexaList.size(); ++i) { + + String lexa = lexaList.get(i); + if (!lexa.contains("REF_")) continue; + lexaList.add(i + 1, lexa); + lexaList.add(i + 2, lexa); + lexaList.add(i + 3, lexa); + lexaList.add(i + 4, lexa); + i += 4; + } + + // calc label pos & remove labels + Map labels = new HashMap<>(); + + for (int i = 0; i < lexaList.size(); ++i) { + + String lexa = lexaList.get(i); + if (!lexa.contains("LABEL_")) continue; + + String label = lexaList.remove(i); + String labelNum = label.split("LABEL_")[1]; + + int labelPos = i; + + labels.put(labelNum, labelPos); + --i; + } + + // encode all ref occurrence + for (int i = 0; i < lexaList.size(); ++i) { + + String lexa = lexaList.get(i); + if (!lexa.contains("REF_")) continue; + + String ref = lexaList.remove(i); + lexaList.remove(i); + lexaList.remove(i); + lexaList.remove(i); + lexaList.remove(i); + String labelNum = ref.split("REF_")[1]; + + Integer pos = labels.get(labelNum); + int bytesNum = ByteUtil.numBytes(pos.toString()); + + lexaList.add(i, pos.toString()); + + for (int j = 0; j < (4 - bytesNum); ++j) + lexaList.add(i, "0"); + + lexaList.add(i, "PUSH4"); + ++i; + } + + for (String lexa : lexaList) { + + if (OpCode.contains(lexa)) + baos.write(OpCode.byteVal(lexa)); + else { + // TODO: support for number more than one byte + baos.write(Integer.parseInt(lexa) & 0xFF); + } + } + + // wrap plan + // 1) that is the code + // 2) need to wrap it with PUSH1 (codesize) PUSH1 (codestart) 000 CODECOPY 000 PUSH1 (codesize) _fURN + + return baos.toByteArray(); + } + + /** + * Return encoded bytes. + */ + public static byte[] encodeMachineCodeForVMRun(byte[] code, byte[] init) { + + if (code == null || code.length == 0) throw new RuntimeException("code can't be empty code: " + code); + + int numBytes = ByteUtil.numBytes(code.length + ""); + byte[] lenBytes = BigIntegers.asUnsignedByteArray(BigInteger.valueOf(code.length)); + + StringBuilder sb = new StringBuilder(); + for (byte lenByte : lenBytes) { + sb.append(lenByte).append(" "); + } + + // calc real code start position (after the init header) + int pos = 10 + numBytes * 2; + if (init != null) pos += init.length; + + // @push_len @len PUSH1 @src_start PUSH1 0 CODECOPY @push_len @len 0 PUSH1 0 RETURN + String header = String.format("[asm %s %s PUSH1 %d PUSH1 0 CODECOPY %s %s PUSH1 0 RETURN asm]", + "PUSH" + numBytes, sb.toString(), pos, "PUSH" + numBytes, sb.toString()); + + byte[] headerMachine = compileAssemblyToMachine(header); + + return init != null ? Arrays.concatenate(init, headerMachine, code) : + Arrays.concatenate(headerMachine, code); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/serpent/SerpentToAssemblyCompiler.java b/ethereumj-core/src/main/java/org/ethereum/serpent/SerpentToAssemblyCompiler.java new file mode 100644 index 00000000..31f5f43b --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/serpent/SerpentToAssemblyCompiler.java @@ -0,0 +1,778 @@ +package org.ethereum.serpent; + +import org.ethereum.crypto.HashUtil; + +import org.antlr.v4.runtime.misc.NotNull; + +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Roman Mandeleil + * @since 05.05.14 + */ +public class SerpentToAssemblyCompiler extends SerpentBaseVisitor { + + private int labelIndex = 0; + private List vars = new ArrayList<>(); + + private Map arraysSize = new HashMap<>(); + private List arraysIndex = new ArrayList<>(); + + @Override + public String visitParse(@NotNull SerpentParser.ParseContext ctx) { + + String codeBlock = visit(ctx.block()); + int memSize = vars.size() * 32 - (vars.size() > 0 ? 1 : 0); + + String initMemCodeBlock = ""; + if (!arraysSize.isEmpty() && vars.size() > 0) + initMemCodeBlock = String.format(" 0 %d MSTORE8 ", memSize); + + if (memSize == 0) + codeBlock = codeBlock.replace("@vars_table@", "0"); + else + codeBlock = codeBlock.replace("@vars_table@", memSize + 1 + ""); + + return initMemCodeBlock + codeBlock; + } + + @Override + public String visitParse_init_code_block(@NotNull SerpentParser.Parse_init_code_blockContext ctx) { + + String initBlock = visit(ctx.block(0)); + int memSize = vars.size() * 32 - (vars.size() > 0 ? 1 : 0); + + String initMemInitBlock = ""; + if (!arraysSize.isEmpty() && vars.size() > 0) + initMemInitBlock = String.format(" 0 %d MSTORE8 ", memSize); + + if (memSize == 0) + initBlock = initBlock.replace("@vars_table@", "0"); + else + initBlock = initBlock.replace("@vars_table@", memSize + 1 + ""); + + vars.clear(); + String codeBlock = visit(ctx.block(1)); + memSize = vars.size() * 32 - (vars.size() > 0 ? 1 : 0); + + if (memSize == 0) + codeBlock = codeBlock.replace("@vars_table@", "0"); + else + codeBlock = codeBlock.replace("@vars_table@", memSize + 1 + ""); + + + String initMemCodeBlock = ""; + if (!arraysSize.isEmpty() && vars.size() > 0) + initMemCodeBlock = String.format(" 0 %d MSTORE8 ", memSize); + + return String.format(" [init %s %s init] [code %s %s code] ", initMemInitBlock, initBlock, + initMemCodeBlock, codeBlock); + } + + @Override + public String visitIf_elif_else_stmt(@NotNull SerpentParser.If_elif_else_stmtContext ctx) { + + StringBuilder retCode = new StringBuilder(); + + int endOfStmtLabel = labelIndex++; + + // if body + SerpentParser.BlockContext ifBlock = (SerpentParser.BlockContext) ctx.getChild(4); + String blockCode = visitBlock(ifBlock); + String ifCond = visitCondition(ctx.condition(0)); + + int nextLabel = labelIndex++; + + // if_cond [NOT REF_X JUMPI] if_body [REF_Y JUMP LABEL_X] Y=total_end + retCode.append(ifCond). + append(" NOT REF_").append(nextLabel).append(" JUMPI "). + append(blockCode). + append("REF_").append(endOfStmtLabel). + append(" JUMP LABEL_").append(nextLabel).append(" "); + + // traverse the children and find out all [elif] and [else] that exist + int count = ctx.condition().size(); + + // traverse all 'elif' statements + int i = 1; // define i here for 'else' option + for (; i < count; ++i) { + + // if the condition much at the end jump out of the main if + // append to general retCode + // TODO: [NOT REF_X JUMPI] elif_body [REF_Y JUMP LABEL_X] Y=total_end + // TODO extract the condition and the body + nextLabel = labelIndex++; + + // elif condition + String elifCond = visitCondition(ctx.condition(i)); + + // elif body + String elifBlockCode = visitBlock(ctx.block(i)); + + retCode.append(elifCond). + append(" NOT REF_").append(nextLabel).append(" JUMPI "). + append(elifBlockCode). + append("REF_").append(endOfStmtLabel).append(" JUMP LABEL_"). + append(nextLabel).append(" "); + } + + // check if there is 'else' + if (ctx.block(i) != null) { + + // body + String elseBlockCode = visitBlock(ctx.block(i)); + + // append to general retCode + retCode.append(elseBlockCode); + } + + // [LABEL_Y] Y = end of statement + retCode.append("LABEL_").append(endOfStmtLabel).append(" "); + + return retCode.toString(); + } + + @Override + public String visitWhile_stmt(@NotNull SerpentParser.While_stmtContext ctx) { + + int whileStartRef = labelIndex++; + int whileEndRef = labelIndex++; + + // elif condition + SerpentParser.ConditionContext whileCondition = (SerpentParser.ConditionContext) + ctx.getChild(1); + + // elif body + SerpentParser.BlockContext whileBlock = (SerpentParser.BlockContext) + ctx.getChild(4); + + return String.format(" LABEL_%d %s NOT REF_%d JUMPI %s REF_%d JUMP LABEL_%d ", + whileStartRef, visitCondition(whileCondition), whileEndRef, visitBlock(whileBlock), + whileStartRef, whileEndRef); + } + + @Override + public String visitBlock(@NotNull SerpentParser.BlockContext ctx) { + + return super.visitBlock(ctx); + } + + @Override + public String visitCondition(@NotNull SerpentParser.ConditionContext ctx) { + return super.visitCondition(ctx); + } + + @Override + public String visitGet_var(@NotNull SerpentParser.Get_varContext ctx) { + + String varName = ctx.VAR().toString(); + int addr; + + addr = vars.indexOf(varName); + if (addr == -1) { + throw new UnassignVarException(varName); + } + return String.format(" %d MLOAD ", addr * 32); + } + + @Override + public String visitAssign(@NotNull SerpentParser.AssignContext ctx) { + + String varName = ctx.VAR().toString(); + + // msg assigned has two arrays to calc + if (ctx.msg_func() != null) { + + return visitMsg_func(ctx.msg_func(), varName); + } else if (ctx.arr_def() != null) { + // if it's an array the all management is different + String arrayCode = visitArr_def(ctx.arr_def()); + + // calc the pointer addr + int pos = getArraySize(arrayCode); + arraysSize.put(varName, pos); + arraysIndex.add(varName); + + return arrayCode; + } else { + String expression = visitExpression(ctx.expression()); + int addr = vars.indexOf(varName); + if (addr == -1) { + addr = vars.size(); + vars.add(varName); + } + return String.format(" %s %d MSTORE ", expression, addr * 32); + } + } + + @Override + public String visitContract_storage_load(@NotNull SerpentParser.Contract_storage_loadContext ctx) { + + String operand0 = visitExpression(ctx.expression()); + + return String.format(" %s SLOAD ", operand0); + } + + @Override + public String visitContract_storage_assign(@NotNull SerpentParser.Contract_storage_assignContext ctx) { + + String operand0 = visitExpression(ctx.expression(0)); + String operand1 = visitExpression(ctx.expression(1)); + + return String.format(" %s %s SSTORE ", operand1, operand0); + } + + @Override + public String visitInt_val(@NotNull SerpentParser.Int_valContext ctx) { + + if (ctx.OP_NOT() != null) + return visitExpression(ctx.expression()) + " NOT"; + + if (ctx.expression() != null) + return visitExpression(ctx.expression()); + + if (ctx.get_var() != null) + return visitGet_var(ctx.get_var()); + + if (ctx.hex_num() != null) + return hexStringToDecimalString(ctx.hex_num().getText()); + + if (ctx.special_func() != null) + return visitSpecial_func(ctx.special_func()); + + if (ctx.msg_data() != null) + return visitMsg_data(ctx.msg_data()); + + if (ctx.contract_storage_load() != null) + return visitContract_storage_load(ctx.contract_storage_load()); + + if (ctx.send_func() != null) + return visitSend_func(ctx.send_func()); + + if (ctx.array_retreive() != null) + return visitArray_retreive(ctx.array_retreive()); + + return ctx.INT().toString(); + } + + @Override + public String visitArr_def(@NotNull SerpentParser.Arr_defContext ctx) { + + List numElements = ctx.int_val(); + int arraySize = numElements.size() * 32 + 32; + + StringBuffer arrayInit = new StringBuffer(); + int i = 32; + for (SerpentParser.Int_valContext int_val : ctx.int_val()) { + + arrayInit.append(String.format(" DUP %d ADD %s SWAP MSTORE ", i, visit(int_val))); + i += 32; + } + + return String.format(" MSIZE 32 ADD MSIZE %s %d SWAP MSTORE ", arrayInit, arraySize); + } + + @Override + public String visitArray_assign(@NotNull SerpentParser.Array_assignContext ctx) { + + int order = this.arraysIndex.indexOf(ctx.VAR().toString()); + if (order == -1) { + throw new Error("array with that name was not defined"); + } + + //calcAllocatedBefore(); + int allocSize = 0; + for (int i = 0; i < order; ++i) { + String var = arraysIndex.get(i); + allocSize += arraysSize.get(var); + } + + String index = visit(ctx.int_val()); + String assignValue = visit(ctx.expression()); + + return String.format(" %s 32 %s MUL 32 ADD %d ADD @vars_table@ ADD MSTORE ", assignValue, index, allocSize); + } + + @Override + public String visitArray_retreive(@NotNull SerpentParser.Array_retreiveContext ctx) { + + int order = this.arraysIndex.indexOf(ctx.VAR().toString()); + if (order == -1) { + throw new Error("array with that name was not defined"); + } + + int allocSize = 32; + for (int i = 0; i < order; ++i) { + String var = arraysIndex.get(i); + allocSize += arraysSize.get(var); + } + + String index = visit(ctx.int_val()); + + return String.format(" 32 %s MUL %d ADD @vars_table@ ADD MLOAD ", index, allocSize); + } + + @Override + public String visitMul_expr(@NotNull SerpentParser.Mul_exprContext ctx) { + if (ctx.mul_expr() == null) return visit(ctx.int_val()); + + String operand0 = visit(ctx.int_val()); + String operand1 = visit(ctx.mul_expr()); + + switch (ctx.OP_MUL().getText().toLowerCase()) { + case "*": + return operand0 + " " + operand1 + " MUL"; + case "/": + return operand0 + " " + operand1 + " DIV"; + case "^": + return operand0 + " " + operand1 + " EXP"; + case "%": + return operand0 + " " + operand1 + " MOD"; + case "#/": + return operand0 + " " + operand1 + " SDIV"; + case "#%": + return operand0 + " " + operand1 + " SMOD"; + default: + throw new UnknownOperandException(ctx.OP_MUL().getText()); + } + } + + @Override + public String visitAdd_expr(@NotNull SerpentParser.Add_exprContext ctx) { + + if (ctx.add_expr() == null) return visit(ctx.mul_expr()); + + String operand0 = visit(ctx.mul_expr()); + String operand1 = visit(ctx.add_expr()); + + switch (ctx.OP_ADD().getText().toLowerCase()) { + case "+": + return operand0 + " " + operand1 + " ADD"; + case "-": + return operand0 + " " + operand1 + " SUB"; + default: + throw new UnknownOperandException(ctx.OP_ADD().getText()); + } + } + + @Override + public String visitRel_exp(@NotNull SerpentParser.Rel_expContext ctx) { + + if (ctx.rel_exp() == null) return visit(ctx.add_expr()); + + String operand0 = visit(ctx.rel_exp()); + String operand1 = visit(ctx.add_expr()); + + switch (ctx.OP_REL().getText().toLowerCase()) { + case "<": + return operand1 + " " + operand0 + " LT"; + case ">": + return operand1 + " " + operand0 + " GT"; + case ">=": + return operand1 + " " + operand0 + " LT NOT"; + case "<=": + return operand1 + " " + operand0 + " GT NOT"; + default: + throw new UnknownOperandException(ctx.OP_REL().getText()); + } + } + + @Override + public String visitEq_exp(@NotNull SerpentParser.Eq_expContext ctx) { + + if (ctx.eq_exp() == null) return visit(ctx.rel_exp()); + + String operand0 = visit(ctx.rel_exp()); + String operand1 = visit(ctx.eq_exp()); + + switch (ctx.OP_EQ().getText().toLowerCase()) { + case "==": + return operand0 + " " + operand1 + " EQ"; + case "!=": + return operand0 + " " + operand1 + " EQ NOT"; + default: + throw new UnknownOperandException(ctx.OP_EQ().getText()); + } + } + + @Override + public String visitAnd_exp(@NotNull SerpentParser.And_expContext ctx) { + + if (ctx.and_exp() == null) return visit(ctx.eq_exp()); + + String operand0 = visit(ctx.eq_exp()); + String operand1 = visit(ctx.and_exp()); + + switch (ctx.OP_AND().getText().toLowerCase()) { + case "&": + return operand0 + " " + operand1 + " AND"; + default: + throw new UnknownOperandException(ctx.OP_AND().getText()); + } + } + + @Override + public String visitEx_or_exp(@NotNull SerpentParser.Ex_or_expContext ctx) { + + if (ctx.ex_or_exp() == null) return visit(ctx.and_exp()); + + String operand0 = visit(ctx.and_exp()); + String operand1 = visit(ctx.ex_or_exp()); + + switch (ctx.OP_EX_OR().getText().toLowerCase()) { + case "xor": + return operand0 + " " + operand1 + " XOR"; + default: + throw new UnknownOperandException(ctx.OP_EX_OR().getText()); + } + } + + @Override + public String visitIn_or_exp(@NotNull SerpentParser.In_or_expContext ctx) { + + if (ctx.in_or_exp() == null) return visit(ctx.ex_or_exp()); + + String operand0 = visit(ctx.ex_or_exp()); + String operand1 = visit(ctx.in_or_exp()); + + switch (ctx.OP_IN_OR().getText().toLowerCase()) { + case "|": + return operand0 + " " + operand1 + " OR"; + default: + throw new UnknownOperandException(ctx.OP_IN_OR().getText()); + } + } + + @Override + public String visitLog_and_exp(@NotNull SerpentParser.Log_and_expContext ctx) { + + if (ctx.log_and_exp() == null) return visit(ctx.in_or_exp()); + + String operand0 = visit(ctx.in_or_exp()); + String operand1 = visit(ctx.log_and_exp()); + + switch (ctx.OP_LOG_AND().getText().toLowerCase()) { + case "and": + return operand0 + " " + operand1 + " NOT NOT MUL"; + case "&&": + return operand0 + " " + operand1 + " NOT NOT MUL"; + default: + throw new UnknownOperandException(ctx.OP_LOG_AND().getText()); + } + } + + @Override + public String visitLog_or_exp(@NotNull SerpentParser.Log_or_expContext ctx) { + + if (ctx.log_or_exp() == null) return visit(ctx.log_and_exp()); + + String operand0 = visit(ctx.log_and_exp()); + String operand1 = visit(ctx.log_or_exp()); + + switch (ctx.OP_LOG_OR().getText().toLowerCase()) { + case "||": + return operand0 + " " + operand1 + " DUP 4 PC ADD JUMPI POP SWAP POP"; + case "or": + return operand0 + " " + operand1 + " DUP 4 PC ADD JUMPI POP SWAP POP"; + default: + throw new UnknownOperandException(ctx.OP_LOG_OR().getText()); + } + } + + @Override + public String visitMsg_sender(@NotNull SerpentParser.Msg_senderContext ctx) { + return "CALLER"; + } + + @Override + public String visitMsg_datasize(@NotNull SerpentParser.Msg_datasizeContext ctx) { + return "32 CALLDATASIZE DIV "; + } + + @Override + public String visitMsg_value(@NotNull SerpentParser.Msg_valueContext ctx) { + return " CALLVALUE "; + } + + @Override + public String visitContract_balance(@NotNull SerpentParser.Contract_balanceContext ctx) { + return " BALANCE "; + } + + @Override + public String visitContract_address(@NotNull SerpentParser.Contract_addressContext ctx) { + return " ADDRESS "; + } + + @Override + public String visitTx_origin(@NotNull SerpentParser.Tx_originContext ctx) { + return " ORIGIN "; + } + + @Override + public String visitBlock_timestamp(@NotNull SerpentParser.Block_timestampContext ctx) { + return " TIMESTAMP "; + } + + @Override + public String visitBlock_number(@NotNull SerpentParser.Block_numberContext ctx) { + return " NUMBER "; + } + + @Override + public String visitTx_gas(@NotNull SerpentParser.Tx_gasContext ctx) { + return " GAS "; + } + + @Override + public String visitBlock_difficulty(@NotNull SerpentParser.Block_difficultyContext ctx) { + return " DIFFICULTY "; + } + + @Override + public String visitBlock_coinbase(@NotNull SerpentParser.Block_coinbaseContext ctx) { + return " COINBASE "; + } + + @Override + public String visitTx_gasprice(@NotNull SerpentParser.Tx_gaspriceContext ctx) { + return " GASPRICE "; + } + + @Override + public String visitBlock_prevhash(@NotNull SerpentParser.Block_prevhashContext ctx) { + return " PREVHASH "; + } + + @Override + public String visitBlock_gaslimit(@NotNull SerpentParser.Block_gaslimitContext ctx) { + return " GASLIMIT "; + } + + @Override + public String visitRet_func_1(@NotNull SerpentParser.Ret_func_1Context ctx) { + + String operand0 = visit(ctx.expression()); + + return String.format(" %s MSIZE SWAP MSIZE MSTORE 32 SWAP RETURN ", operand0); + } + + @Override + public String visitRet_func_2(@NotNull SerpentParser.Ret_func_2Context ctx) { + + String operand0 = visit(ctx.expression(0)); + String operand1 = visit(ctx.expression(1)); + + return String.format(" %s 32 MUL %s RETURN ", operand1, operand0); + } + + @Override + public String visitSuicide_func(@NotNull SerpentParser.Suicide_funcContext ctx) { + + String operand0 = visit(ctx.expression()); + + return String.format(" %s SUICIDE ", operand0); + } + + @Override + public String visitStop_func(@NotNull SerpentParser.Stop_funcContext ctx) { + return " STOP "; + } + + @Override + public String visitMsg_data(@NotNull SerpentParser.Msg_dataContext ctx) { + + String operand0 = visit(ctx.expression()); + + return String.format("%s 32 MUL CALLDATALOAD ", operand0); + } + + public String visitMsg_func(@NotNull SerpentParser.Msg_funcContext ctx, String varName) { + +// msg_func: 'msg' '(' int_val ',' int_val ',' int_val ',' arr_def ',' int_val ',' int_val')' ; +// msg_func: 'msg' '(' [gas] ',' [to] ',' [value] ',' arr_def ',' [in_len] ',' [out_len]')' ; + + String operand0 = visit(ctx.int_val(0)); + String operand1 = visit(ctx.int_val(1)); + String operand2 = visit(ctx.int_val(2)); + + String loadInData = visit(ctx.arr_def()); + + String inSizeCallParam = visit(ctx.int_val(3)); + String outSizeCallParam = visit(ctx.int_val(4)); + + // TODO: 1. allocate out_memory and push ptr + // TODO: 2. push ptr for in_memory allocated + + String randomArrayName = new String(HashUtil.randomPeerId()); + + int inSize = Integer.parseInt(inSizeCallParam); + int outSize = Integer.parseInt(outSizeCallParam); + + arraysSize.put(randomArrayName, inSize * 32 + 32); + arraysIndex.add(randomArrayName); + + int outSizeVal = outSize * 32 + 32; + arraysSize.put(varName, outSize * 32 + 32); + arraysIndex.add(varName); + +// [OUTDATASIZE] [OUTDATASTART] [INDATASIZE] [INDATASTART] [VALUE] [TO] [GAS] CALL +// [OUTDATASIZE] [OUTDATASTART] [INDATASIZE] [INDATASTART] ***ARR_IN_SET*** [VALUE] [TO] [GAS] CALL + //X_X = [ 32 + 128 + 6 * 32 ] = [ var_table_size + in_arr_size + out_arr_size ] + + // this code allocates the memory block for the out data, + // and saves the size in typical array format [size, element_1, element_2, ...] + String outArrSet = String.format(" %d MSIZE MSTORE 0 %d MSIZE ADD MSTORE8 ", outSizeVal, outSizeVal - 32); + + return String.format("%d MSIZE %s %d %s %s %s %s CALL ", + outSizeVal, outArrSet, inSize * 32, loadInData, operand2, operand1, operand0); + } + + @Override + public String visitSend_func(@NotNull SerpentParser.Send_funcContext ctx) { + + String operand0 = visit(ctx.int_val(0)); + String operand1 = visit(ctx.int_val(1)); + String operand2 = visit(ctx.int_val(2)); + +// OUTDATASIZE OUTDATASTART INDATASIZE INDATASTART VALUE TO GAS CALL + return String.format("0 0 0 0 %s %s %s CALL ", operand2, operand1, operand0); + } + + @Override + public String visitCreate_func(@NotNull SerpentParser.Create_funcContext ctx) { + String operand0 = visit(ctx.int_val(0)); + String operand1 = visit(ctx.int_val(1)); + String operand2 = visit(ctx.int_val(2)); + +// MEM_SIZE MEM_START GAS CREATE + return String.format(" %s %s %s CREATE ", operand2, operand1, operand0); + } + + @Override + public String visitAsm(@NotNull SerpentParser.AsmContext ctx) { + + int size = ctx.asm_symbol().getChildCount(); + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < size; ++i) { + + String symbol = ctx.asm_symbol().children.get(i).toString(); + symbol = symbol.trim(); + + // exclude all that is not an assembly code + if (symbol.equals("[asm") || symbol.equals("asm]") || symbol.equals("\n")) continue; + + boolean match = Pattern.matches("[0-9]+", symbol); + if (match) { + + int byteVal = Integer.parseInt(symbol); + if (byteVal > 255 || byteVal < 0) + throw new Error("In the [asm asm] block should be placed only byte range numbers"); + } + + match = Pattern.matches("0[xX][0-9a-fA-F]+", symbol); + if (match) { + + int byteVal = Integer.parseInt(symbol.substring(2), 16); + if (byteVal > 255 || byteVal < 0) + throw new Error("In the [asm asm] block should be placed only byte range numbers"); + symbol = byteVal + ""; + } + + sb.append(symbol).append(" "); + } + + return "[asm " + sb.toString() + " asm]"; + } + + @Override + protected String aggregateResult(String aggregate, String nextResult) { + return aggregate + nextResult; + } + + @Override + protected String defaultResult() { + return ""; + } + + /** + * @param hexNum should be in form '0x34fabd34....' + */ + private String hexStringToDecimalString(String hexNum) { + String digits = hexNum.substring(2); + if (digits.length() % 2 != 0) digits = "0" + digits; + byte[] numberBytes = Hex.decode(digits); + return (new BigInteger(1, numberBytes)).toString(); + } + + public static class UnknownOperandException extends RuntimeException { + public UnknownOperandException(String name) { + super("unknown operand: " + name); + } + } + + public static class UnassignVarException extends RuntimeException { + public UnassignVarException(String name) { + super("attempt to access not assigned variable: " + name); + } + } + + private static Integer getMsgOutputArraySize(String code) { + + String result = "0"; + Pattern pattern = Pattern.compile(""); + Matcher matcher = pattern.matcher(code.trim()); + if (matcher.find()) { + + String group = matcher.group(0); + result = group.replaceAll("", "$1").trim(); + } + return Integer.parseInt(result); + } + + private static Integer getMsgInputArraySize(String code) { + + String result = "0"; + Pattern pattern = Pattern.compile(""); + Matcher matcher = pattern.matcher(code.trim()); + if (matcher.find()) { + + String group = matcher.group(0); + result = group.replaceAll("", "$1").trim(); + } + return Integer.parseInt(result); + } + + private static String cleanMsgString(String code) { + return code.replaceAll("<(.*?)>", ""); + } + + + /** + * After the array deff code is set + * extract the size out of code string + */ + private static Integer getArraySize(String code) { + + String result = "0"; + Pattern pattern = Pattern.compile(" [0-9]* SWAP MSTORE$"); + Matcher matcher = pattern.matcher(code.trim()); + if (matcher.find()) { + + String group = matcher.group(0); + result = group.replace("SWAP MSTORE", "").trim(); + } + return Integer.parseInt(result); + } + +}