/* * Copyright 2007-2013 * Licensed under GNU Lesser General Public License * * This file is part of EpochX: genetic programming software for research * * EpochX is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * EpochX is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with EpochX. If not, see <http://www.gnu.org/licenses/>. * * The latest version is available from: http://www.epochx.org */ package org.epochx.interpret; import java.util.Arrays; import org.epochx.Individual; import org.epochx.source.SourceGenerator; /** * A BrainfuckInterpreter provides the facility to execute programs in the * esoteric Brainfuck programming language. Memory is provided in the form of * a 30,000 element byte array which the programs manipulate. The * <code>eval</code> interpreter functions are not supported since Brainfuck * provides no expressions that can be evaluated. The <code>exec</code> methods * should be used, with the memory retrievable after execution. The * <code>argValues</code> given to the <code>exec</code> methods will be used to * populate the first elements of the memory array in sequence. The * <code>argNames</code> array is not used. * * <h4>Supported language syntax</h4> * * <table> * <tr> * <th>Syntax</th> * <th>Effect</th> * </tr> * <tr> * <td><</td> * <td>Increments the pointer. The pointer wraps around to 0 if it is to become * larger than the memory capacity.</td> * </tr> * <tr> * <td>></td> * <td>Decrements the pointer. The pointer wraps around to point to the last * memory address if it would otherwise become negative.</td> * </tr> * <tr> * <td>+</td> * <td>Increments the value of the memory location addressed by the current * pointer.</td> * </tr> * <tr> * <td>-</td> * <td>Decrements the value of the memory location addressed by the current * pointer.</td> * </tr> * <tr> * <td>,</td> * <td>Not currently supported.</td> * </tr> * <tr> * <td>.</td> * <td>Not currently supported.</td> * </tr> * <tr> * <td>[</td> * <td>If value of current memory location is 0 then jump forward to matching * ']'.</td> * </tr> * <tr> * <td>]</td> * <td>Jump back to just before matching '['.</td> * </tr> * </table> * * @since 2.0 */ public class BrainfuckInterpreter<T extends Individual> implements Interpreter<T> { // The indexable memory available to programs. private final byte[] memory; // Pointer to current memory address. private int pointer; private SourceGenerator<T> generator; /** * Constructs a BrainfuckInterpreter with a 30,000 element byte array for * memory. * * @param generator the SourceGenerator to use to convert individuals to Brainfuck * source code */ public BrainfuckInterpreter(SourceGenerator<T> generator) { this(generator, 30000); } /** * Constructs a BrainfuckInterpreter with a byte array for memory with the * given capacity. * * @param generator the SourceGenerator to use to convert individuals to Brainfuck * source code * @param memorySize the size of the byte array to provide programs with for memory */ public BrainfuckInterpreter(SourceGenerator<T> generator, int memorySize) { memory = new byte[memorySize]; pointer = 0; } /* * Resets the memory array to be filled with 0 bytes, and the pointer to * address element 0. */ private void reset() { Arrays.fill(memory, (byte) 0); pointer = 0; } /** * Not currently supported by BrainfuckInterpreter. Calling will throw an * IllegalStateException. */ @Override public Object[] eval(T program, String[] argNames, Object[][] argValues) { throw new IllegalStateException("method not supported"); } /** * Executes the given Brainfuck program upon the memory byte array. The * given <code>argValues</code> will be used to populate the first elements * of the memory array in sequence. All other elements of the memory array * will be set to 0 byte and the pointer will also be reset to address 0 * before execution. The <code>argNames</code> argument is not used. * * @param program a valid Brainfuck program that is to be executed * @param argNames not used in this implementation * @param argValues an array of values which can be considered the inputs to * the program. They will populate the first elements of the memory * array in * sequence before execution starts. */ @Override public void exec(T program, String[] argNames, Object[][] argValues) { int noParamSets = argValues.length; for (int i=0; i<noParamSets; i++) { Object[] paramSet = argValues[i]; // Reset the environment. reset(); // Set inputs as first x memory cells. for (int j = 0; j < paramSet.length; j++) { memory[i] = (Byte) paramSet[j]; } // Get the program source code. String source = generator.getSource(program); // Execute the source. execute(source); } } /* * Parses and executes the given source string as a Brainfuck program. */ private void execute(final String source) { if (source == null) { return; } for (int i = 0; i < source.length(); i++) { final char c = source.charAt(i); switch (c) { case '>': pointer = ++pointer % memory.length; break; case '<': pointer--; if (pointer < 0) { pointer = memory.length - 1; } break; case '+': memory[pointer]++; break; case '-': memory[pointer]--; break; case ',': // Not supported. break; case '.': // Not supported. // System.out.print((char) memory[pointer]); break; case '[': final int bracketIndex = findClosingBracket(source.substring(i + 1)) + (i + 1); final String loopSource = source.substring((i + 1), bracketIndex); while (memory[pointer] != 0) { execute(loopSource); } i = bracketIndex; break; case ']': // Implemented as part of '['. break; default: // Ignore all other characters. break; } } } /* * Locate the matching bracket in the given source. */ private int findClosingBracket(final String source) { int open = 1; for (int i = 0; i < source.length(); i++) { final char c = source.charAt(i); if (c == '[') { open++; } else if (c == ']') { open--; if (open == 0) { return i; } } } // There is no closing bracket. return -1; } /** * Returns the byte array which is providing indexed memory for the * programs. The array will be cleared for each execution. * * @return the program's indexed memory. */ public byte[] getMemory() { return memory; } /** * Returns the source generator being used to convert individuals to source code. * * @return the current source generator */ public SourceGenerator<T> getSourceGenerator() { return generator; } /** * Sets the source generator to use to convert individuals to source code * * @param the source generator to set */ public void setSourceGenerator(SourceGenerator<T> generator) { this.generator = generator; } }