diff --git a/pom.xml b/pom.xml index d754bbf3..653bbc80 100644 --- a/pom.xml +++ b/pom.xml @@ -137,7 +137,7 @@ SOFTWARE. org.eolang jeo-maven-plugin - 0.2.15 + 0.2.16 com.jcabi diff --git a/src/it/decompile-compile/target/generated-sources/xmir/org/eolang/jeo/Bar.xmir b/src/it/decompile-compile/target/generated-sources/xmir/org/eolang/jeo/Bar.xmir index 8057a05d..2e021dc3 100644 --- a/src/it/decompile-compile/target/generated-sources/xmir/org/eolang/jeo/Bar.xmir +++ b/src/it/decompile-compile/target/generated-sources/xmir/org/eolang/jeo/Bar.xmir @@ -59,7 +59,6 @@ - 00 00 00 00 00 00 00 01 @@ -105,7 +104,6 @@ - diff --git a/src/main/java/org/eolang/opeo/ast/Add.java b/src/main/java/org/eolang/opeo/ast/Add.java index 5b062a37..485cb22c 100644 --- a/src/main/java/org/eolang/opeo/ast/Add.java +++ b/src/main/java/org/eolang/opeo/ast/Add.java @@ -23,6 +23,9 @@ */ package org.eolang.opeo.ast; +import java.util.ArrayList; +import java.util.List; +import org.objectweb.asm.Opcodes; import org.xembly.Directive; import org.xembly.Directives; @@ -65,4 +68,13 @@ public Iterable toXmir() { .append(this.right.toXmir()) .up(); } + + @Override + public List opcodes() { + final List res = new ArrayList<>(0); + res.addAll(this.left.opcodes()); + res.addAll(this.right.opcodes()); + res.add(new Opcode(Opcodes.IADD)); + return res; + } } diff --git a/src/main/java/org/eolang/opeo/ast/AstNode.java b/src/main/java/org/eolang/opeo/ast/AstNode.java index bdd63fb0..3eb0d28b 100644 --- a/src/main/java/org/eolang/opeo/ast/AstNode.java +++ b/src/main/java/org/eolang/opeo/ast/AstNode.java @@ -23,6 +23,7 @@ */ package org.eolang.opeo.ast; +import java.util.List; import org.xembly.Directive; /** @@ -42,4 +43,10 @@ public interface AstNode { * @return XMIR XML. */ Iterable toXmir(); + + /** + * Bytecode instructions. + * @return List of opcodes. + */ + List opcodes(); } diff --git a/src/main/java/org/eolang/opeo/ast/Constructor.java b/src/main/java/org/eolang/opeo/ast/Constructor.java index 6703e860..ca4b71cd 100644 --- a/src/main/java/org/eolang/opeo/ast/Constructor.java +++ b/src/main/java/org/eolang/opeo/ast/Constructor.java @@ -86,6 +86,11 @@ public Iterable toXmir() { return directives.up(); } + @Override + public List opcodes() { + throw new UnsupportedOperationException("Not implemented yet"); + } + /** * Print arguments. * @return Arguments string. diff --git a/src/main/java/org/eolang/opeo/ast/InstanceField.java b/src/main/java/org/eolang/opeo/ast/InstanceField.java index 11a37f1f..d2b21c66 100644 --- a/src/main/java/org/eolang/opeo/ast/InstanceField.java +++ b/src/main/java/org/eolang/opeo/ast/InstanceField.java @@ -23,6 +23,7 @@ */ package org.eolang.opeo.ast; +import java.util.List; import org.xembly.Directive; import org.xembly.Directives; @@ -65,4 +66,9 @@ public Iterable toXmir() { .append(this.source.toXmir()) .up(); } + + @Override + public List opcodes() { + throw new UnsupportedOperationException("Not implemented yet"); + } } diff --git a/src/main/java/org/eolang/opeo/ast/Invocation.java b/src/main/java/org/eolang/opeo/ast/Invocation.java index 82aee8f1..50027944 100644 --- a/src/main/java/org/eolang/opeo/ast/Invocation.java +++ b/src/main/java/org/eolang/opeo/ast/Invocation.java @@ -111,6 +111,11 @@ public Iterable toXmir() { return directives.up(); } + @Override + public List opcodes() { + throw new UnsupportedOperationException("Not implemented yet"); + } + /** * Print arguments. * @return Arguments diff --git a/src/main/java/org/eolang/opeo/ast/Label.java b/src/main/java/org/eolang/opeo/ast/Label.java new file mode 100644 index 00000000..d8402848 --- /dev/null +++ b/src/main/java/org/eolang/opeo/ast/Label.java @@ -0,0 +1,68 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016-2023 Objectionary.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.eolang.opeo.ast; + +import java.util.Collections; +import java.util.List; +import org.xembly.Directive; +import org.xembly.Directives; + +/** + * Label ast node. + * @since 0.1 + */ +public final class Label implements AstNode { + + /** + * Label identifier. + */ + private final AstNode identifier; + + /** + * Constructor. + * @param identifier Label identifier. + */ + public Label(final AstNode identifier) { + this.identifier = identifier; + } + + @Override + public String print() { + return String.format(": %s", this.identifier.print()); + } + + @Override + public Iterable toXmir() { + return new Directives() + .add("o") + .attr("base", "label") + .append(this.identifier.toXmir()) + .up(); + } + + @Override + public List opcodes() { + return Collections.singletonList(this); + } +} diff --git a/src/main/java/org/eolang/opeo/ast/Literal.java b/src/main/java/org/eolang/opeo/ast/Literal.java index 6084fddf..5e8d4ea1 100644 --- a/src/main/java/org/eolang/opeo/ast/Literal.java +++ b/src/main/java/org/eolang/opeo/ast/Literal.java @@ -23,7 +23,10 @@ */ package org.eolang.opeo.ast; +import java.util.Collections; +import java.util.List; import org.eolang.jeo.representation.directives.DirectivesData; +import org.objectweb.asm.Opcodes; import org.xembly.Directive; /** @@ -50,6 +53,19 @@ public Iterable toXmir() { return new DirectivesData(this.object); } + @Override + public List opcodes() { + final List result; + if (this.object instanceof Integer) { + result = Collections.singletonList(Literal.opcode((Integer) this.object)); + } else if (this.object instanceof String) { + result = Collections.singletonList(Literal.opcode((String) this.object)); + } else { + throw new UnsupportedOperationException("Not implemented yet"); + } + return result; + } + @Override public String print() { final String result; @@ -60,4 +76,46 @@ public String print() { } return result; } + + /** + * Convert string into an opcode. + * @param value String value. + * @return Opcode. + */ + private static Opcode opcode(final String value) { + return new Opcode(Opcodes.LDC, value); + } + + /** + * Convert integer into an opcode. + * @param value Integer value. + * @return Opcode. + */ + private static Opcode opcode(final int value) { + final Opcode res; + switch (value) { + case 0: + res = new Opcode(Opcodes.ICONST_0); + break; + case 1: + res = new Opcode(Opcodes.ICONST_1); + break; + case 2: + res = new Opcode(Opcodes.ICONST_2); + break; + case 3: + res = new Opcode(Opcodes.ICONST_3); + break; + case 4: + res = new Opcode(Opcodes.ICONST_4); + break; + case 5: + res = new Opcode(Opcodes.ICONST_5); + break; + default: + res = new Opcode(Opcodes.BIPUSH, value); + break; + } + return res; + } } diff --git a/src/main/java/org/eolang/opeo/ast/Mul.java b/src/main/java/org/eolang/opeo/ast/Mul.java index 7d493547..b966bc63 100644 --- a/src/main/java/org/eolang/opeo/ast/Mul.java +++ b/src/main/java/org/eolang/opeo/ast/Mul.java @@ -23,6 +23,7 @@ */ package org.eolang.opeo.ast; +import java.util.List; import org.xembly.Directive; import org.xembly.Directives; @@ -65,4 +66,9 @@ public Iterable toXmir() { .append(this.right.toXmir()) .up(); } + + @Override + public List opcodes() { + throw new UnsupportedOperationException("Not implemented yet"); + } } diff --git a/src/main/java/org/eolang/opeo/ast/Opcode.java b/src/main/java/org/eolang/opeo/ast/Opcode.java index d30c9ae4..ecd247a6 100644 --- a/src/main/java/org/eolang/opeo/ast/Opcode.java +++ b/src/main/java/org/eolang/opeo/ast/Opcode.java @@ -26,6 +26,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import org.eolang.jeo.representation.directives.DirectivesInstruction; import org.eolang.parser.XMIR; import org.xembly.Directive; @@ -38,6 +39,11 @@ */ public final class Opcode implements AstNode { + /** + * Opcodes counting. + */ + private static final AtomicBoolean COUNTING = new AtomicBoolean(true); + /** * Opcode. */ @@ -82,7 +88,7 @@ public Opcode(final int opcode, final boolean counting) { * @param operands Opcode operands */ public Opcode(final int opcode, final List operands) { - this(opcode, operands, true); + this(opcode, operands, Opcode.COUNTING.get()); } /** @@ -106,4 +112,22 @@ public String print() { public Iterable toXmir() { return new DirectivesInstruction(this.bytecode, this.counting, this.operands.toArray()); } + + @Override + public List opcodes() { + return List.of(this); + } + + /** + * Disable opcodes counting. + * It is useful for tests. + * @todo #65:30min Remove public static method 'disableCounting()' from Opcode. + * Currently it is used in tests. We should find another + * way to disable opcodes counting in tests. When we find + * the way to do it, we should remove this method. + */ + @SuppressWarnings("PMD.ProhibitPublicStaticMethods") + public static void disableCounting() { + Opcode.COUNTING.set(false); + } } diff --git a/src/main/java/org/eolang/opeo/ast/OpcodeName.java b/src/main/java/org/eolang/opeo/ast/OpcodeName.java new file mode 100644 index 00000000..3ce0b58b --- /dev/null +++ b/src/main/java/org/eolang/opeo/ast/OpcodeName.java @@ -0,0 +1,119 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016-2023 Objectionary.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.eolang.opeo.ast; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import org.objectweb.asm.Opcodes; + +/** + * Opcode name. + * The name of bytecode instruction. The name combined with unique number in order to + * avoid name collisions. + * @since 0.1.0 + * @todo #65:30min Code duplication with jeo-maven-plugin. + * This class is a copy of org.eolang.jeo.representation.directives.OpcodeName. + * It should be moved to jeo-maven-plugin and used from there. + */ +public final class OpcodeName { + + /** + * Opcode names. + */ + private static final Map NAMES = OpcodeName.init(); + + /** + * Default counter. + */ + private static final AtomicInteger DEFAULT = new AtomicInteger(0); + + /** + * Bytecode operation code. + */ + private final int opcode; + + /** + * Opcode counter. + */ + private final AtomicInteger counter; + + /** + * Constructor. + * @param opcode Bytecode operation code. + */ + public OpcodeName(final int opcode) { + this(opcode, OpcodeName.DEFAULT); + } + + /** + * Constructor. + * @param opcode Bytecode operation code. + * @param counter Opcode counter. + */ + OpcodeName(final int opcode, final AtomicInteger counter) { + this.opcode = opcode; + this.counter = counter; + } + + /** + * Get simplified opcode name without counter. + * @return Simplified opcode name. + */ + public String simplified() { + return OpcodeName.NAMES.getOrDefault(this.opcode, "UNKNOWN"); + } + + /** + * Get string representation of a bytecode. + * @return String representation of a bytecode. + */ + String asString() { + final String opc = OpcodeName.NAMES.getOrDefault(this.opcode, "UNKNOWN"); + return String.format("%s-%X", opc, this.counter.incrementAndGet()); + } + + /** + * Initialize opcode names. + * @return Opcode names. + */ + private static Map init() { + try { + final Map res = new HashMap<>(); + for (final Field field : Opcodes.class.getFields()) { + if (field.getType() == int.class) { + res.put(field.getInt(Opcodes.class), field.getName()); + } + } + return res; + } catch (final IllegalAccessException exception) { + throw new IllegalStateException( + String.format("Can't retrieve opcode names from '%s'", Opcodes.class), + exception + ); + } + } +} + diff --git a/src/main/java/org/eolang/opeo/ast/Reference.java b/src/main/java/org/eolang/opeo/ast/Reference.java index e07c9555..6253e002 100644 --- a/src/main/java/org/eolang/opeo/ast/Reference.java +++ b/src/main/java/org/eolang/opeo/ast/Reference.java @@ -23,6 +23,7 @@ */ package org.eolang.opeo.ast; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.xembly.Directive; @@ -70,4 +71,9 @@ public String print() { public Iterable toXmir() { return this.ref.get().toXmir(); } + + @Override + public List opcodes() { + throw new UnsupportedOperationException("Not implemented yet"); + } } diff --git a/src/main/java/org/eolang/opeo/ast/Root.java b/src/main/java/org/eolang/opeo/ast/Root.java index 7f55417d..ce9fe467 100644 --- a/src/main/java/org/eolang/opeo/ast/Root.java +++ b/src/main/java/org/eolang/opeo/ast/Root.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.stream.Collectors; import org.xembly.Directive; import org.xembly.Directives; @@ -76,6 +77,11 @@ public Iterable toXmir() { return result; } + @Override + public List opcodes() { + throw new UnsupportedOperationException("Not implemented yet"); + } + /** * Append child. * @param node Child diff --git a/src/main/java/org/eolang/opeo/ast/StoreLocal.java b/src/main/java/org/eolang/opeo/ast/StoreLocal.java index cd232a55..130a5342 100644 --- a/src/main/java/org/eolang/opeo/ast/StoreLocal.java +++ b/src/main/java/org/eolang/opeo/ast/StoreLocal.java @@ -23,6 +23,7 @@ */ package org.eolang.opeo.ast; +import java.util.List; import org.xembly.Directive; import org.xembly.Directives; @@ -69,4 +70,9 @@ public Iterable toXmir() { .append(this.value.toXmir()) .up(); } + + @Override + public List opcodes() { + throw new UnsupportedOperationException("Not implemented yet"); + } } diff --git a/src/main/java/org/eolang/opeo/ast/Super.java b/src/main/java/org/eolang/opeo/ast/Super.java index 5ea8fcba..f09a0a56 100644 --- a/src/main/java/org/eolang/opeo/ast/Super.java +++ b/src/main/java/org/eolang/opeo/ast/Super.java @@ -23,9 +23,11 @@ */ package org.eolang.opeo.ast; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.objectweb.asm.Opcodes; import org.xembly.Directive; import org.xembly.Directives; @@ -83,6 +85,20 @@ public Iterable toXmir() { return directives.up(); } + @Override + public List opcodes() { + final List res = new ArrayList<>(1); + res.addAll(this.instance.opcodes()); + this.arguments.stream().map(AstNode::opcodes).forEach(res::addAll); + //@checkstyle MethodBodyCommentsCheck (10 lines) + // @todo #65:90min Pass correct arguments to invokespecial instruction. + // Currently we just pass default arguments to invokespecial instruction. + // We need to pass correct arguments to invokespecial instruction. + // But in order to implement this we need to save this arguments somewhere before. + res.add(new Opcode(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V")); + return res; + } + /** * Print arguments. * @return Arguments. diff --git a/src/main/java/org/eolang/opeo/ast/This.java b/src/main/java/org/eolang/opeo/ast/This.java index a9ed6eae..a28fd13a 100644 --- a/src/main/java/org/eolang/opeo/ast/This.java +++ b/src/main/java/org/eolang/opeo/ast/This.java @@ -23,6 +23,9 @@ */ package org.eolang.opeo.ast; +import java.util.Collections; +import java.util.List; +import org.objectweb.asm.Opcodes; import org.xembly.Directive; import org.xembly.Directives; @@ -44,4 +47,9 @@ public Iterable toXmir() { return new Directives().add("o").attr("base", "$").up(); } + @Override + public List opcodes() { + return Collections.singletonList(new Opcode(Opcodes.ALOAD, 0)); + } + } diff --git a/src/main/java/org/eolang/opeo/ast/Variable.java b/src/main/java/org/eolang/opeo/ast/Variable.java index cc020711..3af51db9 100644 --- a/src/main/java/org/eolang/opeo/ast/Variable.java +++ b/src/main/java/org/eolang/opeo/ast/Variable.java @@ -23,7 +23,9 @@ */ package org.eolang.opeo.ast; +import java.util.List; import lombok.ToString; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.xembly.Directive; import org.xembly.Directives; @@ -77,4 +79,12 @@ public Iterable toXmir() { .attr("base", String.format("local%d", this.identifier)) .up(); } + + @Override + public List opcodes() { + if (this.type.equals(Type.INT_TYPE)) { + return List.of(new Opcode(Opcodes.ILOAD, this.identifier)); + } + throw new UnsupportedOperationException("Not implemented yet"); + } } diff --git a/src/main/java/org/eolang/opeo/ast/WriteField.java b/src/main/java/org/eolang/opeo/ast/WriteField.java index 4c9f558f..ef510fa1 100644 --- a/src/main/java/org/eolang/opeo/ast/WriteField.java +++ b/src/main/java/org/eolang/opeo/ast/WriteField.java @@ -23,6 +23,7 @@ */ package org.eolang.opeo.ast; +import java.util.List; import org.xembly.Directive; import org.xembly.Directives; @@ -65,4 +66,9 @@ public Iterable toXmir() { .append(this.value.toXmir()) .up(); } + + @Override + public List opcodes() { + throw new UnsupportedOperationException("Not implemented yet"); + } } diff --git a/src/main/java/org/eolang/opeo/compilation/OpeoNodes.java b/src/main/java/org/eolang/opeo/compilation/OpeoNodes.java index 21d335d8..326ef40c 100644 --- a/src/main/java/org/eolang/opeo/compilation/OpeoNodes.java +++ b/src/main/java/org/eolang/opeo/compilation/OpeoNodes.java @@ -27,17 +27,24 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; -import java.util.stream.Stream; +import org.eolang.jeo.representation.xmir.XmlInstruction; import org.eolang.jeo.representation.xmir.XmlNode; +import org.eolang.opeo.ast.Add; import org.eolang.opeo.ast.AstNode; +import org.eolang.opeo.ast.Label; +import org.eolang.opeo.ast.Literal; import org.eolang.opeo.ast.Opcode; -import org.objectweb.asm.Opcodes; +import org.eolang.opeo.ast.Super; +import org.eolang.opeo.ast.This; +import org.eolang.opeo.ast.Variable; +import org.objectweb.asm.Type; import org.xembly.Xembler; /** * High-level representation of Opeo nodes. * @since 0.1 */ +@SuppressWarnings("PMD.AvoidDuplicateLiterals") public final class OpeoNodes { /** @@ -85,61 +92,68 @@ List toJeoNodes() { * @return List of opcodes */ private static List opcodes(final XmlNode node) { - final List result; - //@checkstyle MethodBodyCommentsCheck (10 lines) - // @todo #37:90min Parse AST from high-level XMIR. - // Currently we apply naive algorithm to convert some parts of high-level representation - // to bytecode instructions. - // We should generate AST first and then compile it to bytecode instructions. - // Don't forget to add unit tests. - if (node.hasAttribute("base", ".plus")) { - final List inner = node.children().collect(Collectors.toList()); - result = Stream.of( - OpeoNodes.opcode(new HexString(inner.get(0).text()).decodeAsInt()), - OpeoNodes.opcode(new HexString(inner.get(1).text()).decodeAsInt()), - new Opcode(Opcodes.IADD) - ) - .map(Opcode::toXmir) - .map(Xembler::new) - .map(Xembler::xmlQuietly) - .map(XmlNode::new) - .collect(Collectors.toList()); - } else { - result = Collections.singletonList(node); - } - return result; + return OpeoNodes.node(node).opcodes() + .stream() + .map(AstNode::toXmir) + .map(Xembler::new) + .map(Xembler::xmlQuietly) + .map(XmlNode::new) + .collect(Collectors.toList()); } /** - * Convert integer into an opcode. - * @param value Integer value. - * @return Opcode. + * Convert XmlNode to AstNode. + * @param node XmlNode + * @return Ast node + * @todo #65:90min Add more nodes to the parser. + * Currently we only support addition and integer literals. + * We need to add support for multiplication and many other nodes. + * You can check all the required nodes in the {@link org.eolang.opeo.ast} package. + * To check all correct transformation you can modify 'benchmark' integration test. */ - private static Opcode opcode(final int value) { - final Opcode res; - switch (value) { - case 0: - res = new Opcode(Opcodes.ICONST_0); - break; - case 1: - res = new Opcode(Opcodes.ICONST_1); - break; - case 2: - res = new Opcode(Opcodes.ICONST_2); - break; - case 3: - res = new Opcode(Opcodes.ICONST_3); - break; - case 4: - res = new Opcode(Opcodes.ICONST_4); - break; - case 5: - res = new Opcode(Opcodes.ICONST_5); - break; - default: - res = new Opcode(Opcodes.BIPUSH, value); - break; + private static AstNode node(final XmlNode node) { + final AstNode result; + if (node.hasAttribute("base", ".plus")) { + final List inner = node.children().collect(Collectors.toList()); + final AstNode left = OpeoNodes.node(inner.get(0)); + final AstNode right = OpeoNodes.node(inner.get(1)); + result = new Add(left, right); + } else if (node.hasAttribute("base", "opcode")) { + final XmlInstruction instruction = new XmlInstruction(node.node()); + result = new Opcode(instruction.opcode(), instruction.operands()); + } else if (node.hasAttribute("base", "label")) { + final List inner = node.children().collect(Collectors.toList()); + result = new Label(OpeoNodes.node(inner.get(0))); + } else if (node.hasAttribute("base", "int")) { + result = new Literal(new HexString(node.text()).decodeAsInt()); + } else if (node.hasAttribute("base", "string")) { + result = new Literal(new HexString(node.text()).decode()); + } else if (node.hasAttribute("base", ".super")) { + final List inner = node.children().collect(Collectors.toList()); + final AstNode instance = OpeoNodes.node(inner.get(0)); + final List arguments; + if (inner.size() > 1) { + arguments = inner.subList(1, inner.size()) + .stream() + .map(OpeoNodes::node) + .collect(Collectors.toList()); + } else { + arguments = Collections.emptyList(); + } + result = new Super(instance, arguments); + } else if (node.hasAttribute("base", "$")) { + result = new This(); + } else if (node.hasAttribute("base", "local0")) { + //@checkstyle MethodBodyCommentsCheck (10 lines) + // @todo #65:90min Handle local variables. + // Currently we just treat all the variables as local variable with index 0 + // and type int. We need to handle local variables correctly. + result = new Variable(Type.INT_TYPE, 0); + } else { + throw new IllegalArgumentException( + String.format("Can't recognize node: %n%s%n", node) + ); } - return res; + return result; } } diff --git a/src/main/java/org/eolang/opeo/compilation/OpeoTree.java b/src/main/java/org/eolang/opeo/compilation/OpeoTree.java new file mode 100644 index 00000000..784944f3 --- /dev/null +++ b/src/main/java/org/eolang/opeo/compilation/OpeoTree.java @@ -0,0 +1,31 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016-2023 Objectionary.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.eolang.opeo.compilation; + +/** + * AST tree of the free EO program. + * @since 0.1 + */ +public final class OpeoTree { +} diff --git a/src/test/java/org/eolang/opeo/compilation/CompilerTest.java b/src/test/java/org/eolang/opeo/compilation/CompilerTest.java index 1ba8d908..d1a2916c 100644 --- a/src/test/java/org/eolang/opeo/compilation/CompilerTest.java +++ b/src/test/java/org/eolang/opeo/compilation/CompilerTest.java @@ -28,6 +28,7 @@ import java.nio.file.Path; import org.cactoos.bytes.BytesOf; import org.cactoos.io.ResourceOf; +import org.eolang.opeo.ast.Opcode; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.hamcrest.io.FileMatchers; @@ -61,6 +62,7 @@ void compilesWithFailureSinceInputFolderIsNotFound(@TempDir final Path temp) { @Test void compilesSingleHighLevelXmir(@TempDir final Path temp) throws Exception { + Opcode.disableCounting(); final Path input = temp.resolve("opeo-xmir").resolve("Bar.xmir"); Files.createDirectories(input.getParent()); final byte[] before = new BytesOf(new ResourceOf("xmir/Bar.xmir")).asBytes(); diff --git a/src/test/java/org/eolang/opeo/compilation/JeoCompilerTest.java b/src/test/java/org/eolang/opeo/compilation/JeoCompilerTest.java index 6538f800..bdf4363e 100644 --- a/src/test/java/org/eolang/opeo/compilation/JeoCompilerTest.java +++ b/src/test/java/org/eolang/opeo/compilation/JeoCompilerTest.java @@ -27,6 +27,7 @@ import com.jcabi.xml.XMLDocument; import org.cactoos.io.ResourceOf; import org.cactoos.text.TextOf; +import org.eolang.opeo.ast.Opcode; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; @@ -39,6 +40,7 @@ final class JeoCompilerTest { @Test void compilesSuccessfully() throws Exception { + Opcode.disableCounting(); final XML expected = new XMLDocument( new TextOf(new ResourceOf("xmir/Bar.xmir")).asString() ); diff --git a/src/test/java/org/eolang/opeo/compilation/OpeoNodesTest.java b/src/test/java/org/eolang/opeo/compilation/OpeoNodesTest.java index 8e22738a..19519348 100644 --- a/src/test/java/org/eolang/opeo/compilation/OpeoNodesTest.java +++ b/src/test/java/org/eolang/opeo/compilation/OpeoNodesTest.java @@ -23,13 +23,20 @@ */ package org.eolang.opeo.compilation; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.eolang.jeo.representation.xmir.XmlNode; import org.eolang.opeo.ast.Add; import org.eolang.opeo.ast.Literal; import org.eolang.opeo.ast.Opcode; +import org.eolang.opeo.ast.OpcodeName; +import org.hamcrest.Description; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; +import org.hamcrest.TypeSafeMatcher; import org.junit.jupiter.api.Test; import org.objectweb.asm.Opcodes; @@ -45,17 +52,16 @@ void convertsOpcodesAsIs() { new Opcode(Opcodes.ICONST_0, false), new Opcode(Opcodes.POP, false) ).toJeoNodes(); MatcherAssert.assertThat( - "We expect the first node to be ICONST_0", - nodes.get(0).toString(), - Matchers.equalTo( - "\n 00 00 00 00 00 00 00 03\n\n" - ) + "We expect to retrieve 2 opcodes, but got something else instead: %n%s%n", + nodes, + Matchers.hasSize(2) ); MatcherAssert.assertThat( - "We expect the second node to be POP", - nodes.get(1).toString(), - Matchers.equalTo( - "\n 00 00 00 00 00 00 00 57\n\n" + "We expect to have specific opcodes in right order as is", + nodes, + new HasInstructions( + Opcodes.ICONST_0, + Opcodes.POP ) ); } @@ -74,19 +80,161 @@ void convertsAddition() { Matchers.hasSize(3) ); MatcherAssert.assertThat( - "We expect the first node to be ICONST_1", - nodes.get(0).attribute("name").get(), - Matchers.containsString("ICONST_1") - ); - MatcherAssert.assertThat( - "We expect the second node to be ICONST_2", - nodes.get(1).attribute("name").get(), - Matchers.containsString("ICONST_2") + "We expect to have specific opcodes in right order", + nodes, + new HasInstructions( + Opcodes.ICONST_1, + Opcodes.ICONST_2, + Opcodes.IADD + ) ); + } + + @Test + void convertsDeepAddition() { MatcherAssert.assertThat( - "We expect the third node to be IADD", - nodes.get(2).attribute("name").get(), - Matchers.containsString("IADD") + "We expect to retrieve 7 opcodes, but got something else instead", + new OpeoNodes( + new Add( + new Add( + new Literal(1), + new Literal(2) + ), + new Add( + new Literal(3), + new Literal(4) + ) + ) + ).toJeoNodes(), + new HasInstructions( + Opcodes.ICONST_1, + Opcodes.ICONST_2, + Opcodes.IADD, + Opcodes.ICONST_3, + Opcodes.ICONST_4, + Opcodes.IADD, + Opcodes.IADD + ) ); } + + /** + * Matcher for {@link List} of {@link XmlNode} to have specific instructions. + * @since 0.1 + */ + private static final class HasInstructions extends TypeSafeMatcher> { + + /** + * Expected opcodes. + */ + private final List opcodes; + + /** + * Bag of collected warnings. + */ + private final List warnings; + + /** + * Constructor. + * @param opcodes Expected opcodes. + */ + private HasInstructions(final int... opcodes) { + this(IntStream.of(opcodes).boxed().collect(Collectors.toList())); + } + + /** + * Constructor. + * @param opcodes Expected opcodes. + */ + private HasInstructions(final List opcodes) { + this.opcodes = opcodes; + this.warnings = new ArrayList<>(0); + } + + @Override + public void describeTo(final Description description) { + description.appendText( + String.format( + "Expected to have %d opcodes, but got %d instead. %n%s%n", + this.opcodes.size(), + this.warnings.size(), + String.join("\n", this.warnings) + ) + ); + } + + @Override + public boolean matchesSafely(final List item) { + boolean result = true; + final int size = item.size(); + for (int index = 0; index < size; ++index) { + if (!this.matches(item.get(index), index)) { + result = false; + break; + } + } + return result; + } + + /** + * Check if node matches expected opcode. + * @param node Node. + * @param index Index. + * @return True if node matches expected opcode. + */ + private boolean matches(final XmlNode node, final int index) { + final boolean result; + final String base = node.attribute("base").orElseThrow(); + if (base.equals("opcode")) { + result = this.verifyName(node, index); + } else { + this.warnings.add( + String.format( + "Expected to have opcode at index %d, but got %s instead", + index, + base + ) + ); + result = false; + } + return result; + } + + /** + * Verify opcode name. + * @param node Node. + * @param index Index. + * @return True if opcode name is correct. + */ + private boolean verifyName(final XmlNode node, final int index) { + final boolean result; + final Optional oname = node.attribute("name"); + if (oname.isPresent()) { + final String expected = new OpcodeName(this.opcodes.get(index)).simplified(); + final String name = oname.get(); + if (name.contains(expected)) { + result = true; + } else { + this.warnings.add( + String.format( + "Expected to have opcode with name %s at index %d, but got %s instead", + expected, + index, + name + ) + ); + result = false; + } + } else { + this.warnings.add( + String.format( + "Expected to have opcode name at index %d, but got nothing instead", + index + ) + ); + result = false; + } + return result; + } + } } diff --git a/src/test/resources/xmir/Bar.xmir b/src/test/resources/xmir/Bar.xmir index 05741116..2ca3c9a9 100644 --- a/src/test/resources/xmir/Bar.xmir +++ b/src/test/resources/xmir/Bar.xmir @@ -41,17 +41,17 @@ 38 63 33 33 31 33 39 37 2D 32 35 39 35 2D 34 66 35 64 2D 38 65 62 30 2D 33 62 30 63 34 62 35 34 36 35 66 32 - + 00 00 00 00 00 00 00 19 00 00 00 00 00 00 00 00 - + 00 00 00 00 00 00 00 B7 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 3C 69 6E 69 74 3E 28 29 56 - + 00 00 00 00 00 00 00 B1 @@ -59,7 +59,6 @@ - 00 00 00 00 00 00 00 01 @@ -72,11 +71,11 @@ 62 39 63 64 62 64 63 65 2D 38 35 38 65 2D 34 30 36 32 2D 61 38 39 30 2D 62 32 65 34 30 62 34 36 31 36 61 37 - + 00 00 00 00 00 00 00 15 00 00 00 00 00 00 00 01 - + 00 00 00 00 00 00 00 9E 36 62 34 36 66 61 36 32 2D 32 66 30 65 2D 34 38 34 30 2D 39 34 62 32 2D 66 65 32 64 31 36 35 65 33 35 30 32 @@ -85,19 +84,19 @@ 30 61 30 33 30 39 37 66 2D 30 66 66 65 2D 34 61 38 63 2D 39 64 34 34 2D 64 31 39 39 33 32 61 35 65 35 35 30 - + 00 00 00 00 00 00 00 04 - + 00 00 00 00 00 00 00 AC 36 62 34 36 66 61 36 32 2D 32 66 30 65 2D 34 38 34 30 2D 39 34 62 32 2D 66 65 32 64 31 36 35 65 33 35 30 32 - + 00 00 00 00 00 00 00 05 - + 00 00 00 00 00 00 00 AC