Java tutorial
/* * Copyright (c) 2015 Jakob Hende * * 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 NONINFRINGEMENT. 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.xlrnet.tibaija.processor; import org.antlr.v4.runtime.RuleContext; import org.antlr.v4.runtime.misc.NotNull; import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.math3.complex.Complex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xlrnet.tibaija.antlr.TIBasicBaseVisitor; import org.xlrnet.tibaija.antlr.TIBasicParser; import org.xlrnet.tibaija.exception.IllegalControlFlowException; import org.xlrnet.tibaija.exception.IllegalTypeException; import org.xlrnet.tibaija.exception.InvalidDimensionException; import org.xlrnet.tibaija.exception.TIStopException; import org.xlrnet.tibaija.memory.ListVariable; import org.xlrnet.tibaija.memory.Parameter; import org.xlrnet.tibaija.memory.Value; import org.xlrnet.tibaija.memory.Variables; import org.xlrnet.tibaija.util.CompareUtils; import org.xlrnet.tibaija.util.ContextUtils; import org.xlrnet.tibaija.util.TIMathUtils; import java.util.List; import java.util.Optional; import java.util.Stack; import java.util.stream.Collectors; /** * Full visitor for the TI-Basic language. This class implements all */ public class FullTIBasicVisitor extends TIBasicBaseVisitor { private static final Logger LOGGER = LoggerFactory.getLogger(FullTIBasicVisitor.class); ExecutionEnvironment environment; /** * Sets the internal execution environment. * * @param environment * The new execution environment. */ final public void setEnvironment(ExecutionEnvironment environment) { this.environment = environment; } @Override public Object visitCommand(@NotNull TIBasicParser.CommandContext ctx) { Object result = null; if (ctx.expressionParent() != null) { result = ctx.expressionParent().accept(this); } else if (ctx.statement() != null) { result = ctx.statement().accept(this); } if (result instanceof Optional) { Optional optionalResult = (Optional) result; if (optionalResult.isPresent()) { Value lastResult = (Value) (optionalResult).get(); environment.getWritableMemory().setLastResult(lastResult); } } else if (result != null) { LOGGER.warn("Command returned unexpected object of type {} with value {}", result.getClass().getSimpleName(), result); } if (result != null) { return result; } else { return super.visitCommand(ctx); } } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Object visitCommandFunction(@NotNull TIBasicParser.CommandFunctionContext ctx) { String commandFunctionName = ctx.commandFunctionIdentifier().getText(); Parameter[] parameters = (Parameter[]) ctx.parameterList().accept(this); environment.runRegisteredCommandFunction(commandFunctionName, parameters); return null; } @Override public Object visitCommandList(@NotNull TIBasicParser.CommandListContext ctx) { final List<TIBasicParser.CommandContext> commandList = ctx.command(); final int commandListSize = commandList.size(); Stack<ControlFlowElement> flowElementStack = new Stack<>(); Stack<ControlFlowElement.ControlFlowToken> skipCommandsStack = new Stack<>(); try { for (int commandCounter = 0; commandCounter < commandListSize; commandCounter++) { final TIBasicParser.CommandContext nextCommand = commandList.get(commandCounter); // Skipping logic if (!skipCommandsStack.empty()) { if (nextCommand.isControlFlowStatement) { commandCounter = internalHandleSkipFlowLogic(commandCounter, commandList, skipCommandsStack, nextCommand); } else { LOGGER.debug("Skipping command {}", commandCounter); } } else if (nextCommand.isControlFlowStatement) { commandCounter = internalHandleControlFlowLogic(commandCounter, commandList, flowElementStack, skipCommandsStack, nextCommand); } else { nextCommand.accept(this); } } } catch (TIStopException stop) { LOGGER.debug("Forced program stop in line {} at char {}", stop.getLinenumber(), stop.getCharInLine()); } return null; } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Object visitCommandStatement(@NotNull TIBasicParser.CommandStatementContext ctx) { String commandStatementName = ctx.commandStatementIdentifier().getText(); Parameter[] parameters; if (ctx.parameterList() != null) { parameters = (Parameter[]) ctx.parameterList().accept(this); } else { parameters = new Parameter[0]; } environment.runRegisteredCommandStatement(commandStatementName, parameters); return null; } @Override public Object visitControlFlowStatement(@NotNull TIBasicParser.ControlFlowStatementContext ctx) { return super.visitControlFlowStatement(ctx); } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Object visitDecrementSkipLessStatement(@NotNull TIBasicParser.DecrementSkipLessStatementContext ctx) { int line = ctx.DECREMENT_SKIP_LESS().getSymbol().getLine(); int startIndex = ctx.DECREMENT_SKIP_LESS().getSymbol().getCharPositionInLine(); Variables.NumberVariable numberVariable = Variables .resolveNumberVariable(ctx.numericalVariable().getText()); Value oldVariableValue = environment.getMemory().getNumberVariableValue(numberVariable); Value compareValue = (Value) ctx.expression().accept(this); if (oldVariableValue.hasImaginaryValue()) throw new IllegalTypeException(line, startIndex, "Unexpected imaginary value", Variables.VariableType.NUMBER, Variables.VariableType.NUMBER); Value newVariableValue = environment.runRegisteredExpressionFunction("-", oldVariableValue, Value.ONE) .get(); environment.getWritableMemory().setNumberVariableValue(numberVariable, newVariableValue); // If new (decremented) value is greater than the expected, skip the next command boolean skipNext = CompareUtils.isLessThan(newVariableValue, compareValue); return new ControlFlowElement(line, startIndex, ControlFlowElement.ControlFlowToken.INCREMENT_SKIP_GREATER, !skipNext, true); } @Override public Object visitElseStatement(@NotNull TIBasicParser.ElseStatementContext ctx) { int line = ctx.ELSE().getSymbol().getLine(); int startIndex = ctx.ELSE().getSymbol().getCharPositionInLine(); return new ControlFlowElement(line, startIndex, ControlFlowElement.ControlFlowToken.ELSE, false, false); } @Override public Object visitEndStatement(@NotNull TIBasicParser.EndStatementContext ctx) { int line = ctx.END().getSymbol().getLine(); int startIndex = ctx.END().getSymbol().getCharPositionInLine(); return new ControlFlowElement(line, startIndex, ControlFlowElement.ControlFlowToken.END, false, false); } @Override public Object visitExpression(@NotNull TIBasicParser.ExpressionContext ctx) { // Nothing to do here -> just return the value ... return super.visitExpression(ctx); } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Value visitExpressionFunctionCall(@NotNull TIBasicParser.ExpressionFunctionCallContext ctx) { String functionName = ctx.expressionFunctionIdentifier().getText(); Parameter[] parameters = (Parameter[]) ctx.parameterList().accept(this); return environment.runRegisteredExpressionFunction(functionName, parameters).get(); } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Parameter visitExpressionParameter(@NotNull TIBasicParser.ExpressionParameterContext ctx) { Value value = (Value) ctx.expression().accept(this); return Parameter.value(value); } @Override public Optional<Value> visitExpressionParent(@NotNull TIBasicParser.ExpressionParentContext ctx) { return Optional.of((Value) ctx.expression().accept(this)); } @Override public Object visitExpression_and(@NotNull TIBasicParser.Expression_andContext ctx) { List<? extends RuleContext> contextRules = ctx.expression_compare(); List<String> operators = ctx.operators; return processGenericExpressions(operators, contextRules); } @Override public Value visitExpression_compare(@NotNull TIBasicParser.Expression_compareContext ctx) { List<? extends RuleContext> contextRules = ctx.expression_plus_minus(); List<String> operators = ctx.operators; return processGenericExpressions(operators, contextRules); } @Override public Object visitExpression_conv(@NotNull TIBasicParser.Expression_convContext ctx) { // TODO: Implement conversion (or some kind of a flag?) return super.visitExpression_conv(ctx); } @Override public Value visitExpression_infix(@NotNull TIBasicParser.Expression_infixContext ctx) { List<? extends RuleContext> contextRules = ctx.expression_negation(); List<String> operators = ctx.operators; return processGenericExpressions(operators, contextRules); } @Override public Value visitExpression_mul_div(@NotNull TIBasicParser.Expression_mul_divContext ctx) { List<? extends RuleContext> contextRules = ctx.expression_infix(); List<String> operators = ctx.operators; return processGenericExpressions(operators, contextRules); } @Override public Value visitExpression_negation(@NotNull TIBasicParser.Expression_negationContext ctx) { Value lhs = (Value) ctx.expression_power_root().accept(this); if (ctx.NEGATIVE_MINUS() == null) return lhs; // Return left hand side if no negation is wanted Value rhs = Value.NEGATIVE_ONE; return environment.runRegisteredExpressionFunction("*", lhs, rhs).get(); } @Override public Value visitExpression_or(@NotNull TIBasicParser.Expression_orContext ctx) { List<? extends RuleContext> contextRules = ctx.expression_and(); List<String> operators = ctx.operators; return processGenericExpressions(operators, contextRules); } @Override public Value visitExpression_plus_minus(@NotNull TIBasicParser.Expression_plus_minusContext ctx) { List<String> operators = ctx.operators; List<? extends RuleContext> contextRules = ctx.expression_mul_div(); return processGenericExpressions(operators, contextRules); } @Override public Value visitExpression_postfix(@NotNull TIBasicParser.Expression_postfixContext ctx) { List<String> operators = ctx.operators; if (ctx.expression_preeval() != null) { // Run regular right-associative postfix logic without imaginary parts Value expressionValue = (Value) ctx.expression_preeval().accept(this); for (String op : operators) expressionValue = environment.runRegisteredExpressionFunction(op, expressionValue).get(); return expressionValue; } else { // Run imaginary logic -> e.g. ii == i(i) int imaginaryCount = ctx.IMAGINARY().size() - 1; Value lhs = Value.of(Complex.I); LOGGER.debug("(IMAGINARY) -> {}", lhs.complex()); for (String op : operators) { if (imaginaryCount >= 0) { lhs = environment.runRegisteredExpressionFunction(op, lhs).get(); if (imaginaryCount > 0) { Value before = lhs; lhs = Value.of(lhs.complex().multiply(Complex.I)); LOGGER.debug("(MULTIPLY) {} (IMAGINARY) -> {}", before.complex(), lhs.complex()); imaginaryCount--; } } } // Multiply value with all left I if (imaginaryCount > 0) { final Complex factor = TIMathUtils.imaginaryNthPower(imaginaryCount); final Value before = lhs; lhs = Value.of(lhs.complex().multiply(factor)); LOGGER.debug("(MULTIPLY) {} {} -> {}", before.complex(), factor, lhs.complex()); } return lhs; } } @Override public Value visitExpression_power_root(@NotNull TIBasicParser.Expression_power_rootContext ctx) { List<? extends RuleContext> contextRules = ctx.expression_postfix(); List<String> operators = ctx.operators; return processGenericExpressions(operators, contextRules); } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Object visitExpression_preeval(@NotNull TIBasicParser.Expression_preevalContext ctx) { if (ctx.expression_prefix() != null) return ctx.expression_prefix().accept(this); else if (ctx.expression_value() != null) return ctx.expression_value().accept(this); else throw new UnsupportedOperationException(); } @Override public Value visitExpression_prefix(@NotNull TIBasicParser.Expression_prefixContext ctx) { Value lhs = (Value) ctx.expression_xor().accept(this); if (ctx.operator != null) return environment.runRegisteredExpressionFunction(ctx.operator, lhs).get(); return lhs; } @Override public Value visitExpression_value(@NotNull TIBasicParser.Expression_valueContext ctx) { if (ctx.expression() != null) return (Value) ctx.expression().accept(this); // Expression with parentheses has more than 1 child else return (Value) ctx.getChild(0).accept(this); // All other rules have only one child } @Override public Value visitExpression_xor(@NotNull TIBasicParser.Expression_xorContext ctx) { List<? extends RuleContext> contextRules = ctx.expression_or(); List<String> operators = ctx.operators; return processGenericExpressions(operators, contextRules); } @Override public ControlFlowElement visitForStatement(@NotNull TIBasicParser.ForStatementContext ctx) { final int line = ctx.FOR().getSymbol().getLine(); final int startIndex = ctx.FOR().getSymbol().getStartIndex(); boolean enterLoop; // Enter in THIS iteration (compare actual variable value) boolean isRepeatable; // Enter EVER (start or end can be reached) Value incrementValue = Value.ONE; Variables.NumberVariable numberVariable = (Variables.NumberVariable) ctx.numericalVariable().accept(this); Value variableValue = environment.getMemory().getNumberVariableValue(numberVariable); Value startValue = (Value) ctx.expression(0).accept(this); Value endValue = (Value) ctx.expression(1).accept(this); if (ctx.expression().size() == 3) { incrementValue = (Value) ctx.expression(2).accept(this); } if (variableValue.hasImaginaryValue() || startValue.hasImaginaryValue() || endValue.hasImaginaryValue()) { throw new IllegalTypeException("Value may not be imaginary", Variables.VariableType.NUMBER, Variables.VariableType.NUMBER); } // Determine if the for loop will be entered if (CompareUtils.isGreaterThan(incrementValue, Value.ZERO)) { // Increment positive enterLoop = CompareUtils.isLessOrEqual(variableValue, endValue); isRepeatable = CompareUtils.isLessOrEqual(startValue, endValue); } else if (CompareUtils.isLessThan(incrementValue, Value.ZERO)) { // Increment negative enterLoop = CompareUtils.isGreaterOrEqual(variableValue, endValue); isRepeatable = CompareUtils.isGreaterOrEqual(startValue, endValue); } else { throw new IllegalTypeException("Increment may not be zero", Variables.VariableType.NUMBER, Variables.VariableType.NUMBER); } return new ControlFlowElement(line, startIndex, ControlFlowElement.ControlFlowToken.FOR, enterLoop, isRepeatable); } @Override public JumpingControlFlowElement visitGotoStatement(@NotNull TIBasicParser.GotoStatementContext ctx) { String targetLabel = ctx.labelIdentifier().getText(); int line = ctx.GOTO().getSymbol().getLine(); int startIndex = ctx.GOTO().getSymbol().getCharPositionInLine(); return new JumpingControlFlowElement(line, startIndex, ControlFlowElement.ControlFlowToken.GOTO, targetLabel); } @Override public ControlFlowElement visitIfStatement(@NotNull TIBasicParser.IfStatementContext ctx) { final int line = ctx.IF().getSymbol().getLine(); final int startIndex = ctx.IF().getSymbol().getCharPositionInLine(); Value value = (Value) ctx.expression().accept(this); boolean lastEvaluation = value.bool(); return new ControlFlowElement(line, startIndex, ControlFlowElement.ControlFlowToken.IF, lastEvaluation, false); } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public ControlFlowElement visitIncrementSkipGreaterStatement( @NotNull TIBasicParser.IncrementSkipGreaterStatementContext ctx) { int line = ctx.INCREMENT_SKIP_GREATER().getSymbol().getLine(); int startIndex = ctx.INCREMENT_SKIP_GREATER().getSymbol().getCharPositionInLine(); Variables.NumberVariable numberVariable = Variables .resolveNumberVariable(ctx.numericalVariable().getText()); Value oldVariableValue = environment.getMemory().getNumberVariableValue(numberVariable); Value compareValue = (Value) ctx.expression().accept(this); if (oldVariableValue.hasImaginaryValue()) throw new IllegalTypeException(line, startIndex, "Unexpected imaginary value", Variables.VariableType.NUMBER, Variables.VariableType.NUMBER); Value newVariableValue = environment.runRegisteredExpressionFunction("+", oldVariableValue, Value.ONE) .get(); environment.getWritableMemory().setNumberVariableValue(numberVariable, newVariableValue); // If new (incremented) value is greater than the expected, skip the next command boolean skipNext = CompareUtils.isGreaterThan(newVariableValue, compareValue); return new ControlFlowElement(line, startIndex, ControlFlowElement.ControlFlowToken.INCREMENT_SKIP_GREATER, !skipNext, true); } @Override public Object visitLabelIdentifier(@NotNull TIBasicParser.LabelIdentifierContext ctx) { return super.visitLabelIdentifier(ctx); } @Override public JumpingControlFlowElement visitLabelStatement(@NotNull TIBasicParser.LabelStatementContext ctx) { String targetLabel = ctx.labelIdentifier().getText(); int line = ctx.LABEL().getSymbol().getLine(); int startIndex = ctx.LABEL().getSymbol().getCharPositionInLine(); return new JumpingControlFlowElement(line, startIndex, ControlFlowElement.ControlFlowToken.LABEL, targetLabel); } @Override public Value visitLastResult(@NotNull TIBasicParser.LastResultContext ctx) { return environment.getMemory().getLastResult(); } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Value visitListElementExpression(@NotNull TIBasicParser.ListElementExpressionContext ctx) { int line = ctx.listVariable().LIST_TOKEN().getSymbol().getLine(); int startIndex = ctx.listVariable().LIST_TOKEN().getSymbol().getCharPositionInLine(); ListVariable listVariable = (ListVariable) ctx.listVariable().accept(this); Value index = (Value) ctx.expression().accept(this); double indexValue = index.complex().getReal(); if (index.hasImaginaryValue()) { throw new InvalidDimensionException(line, startIndex, "Index may not be imaginary", index); } if (indexValue % 1 != 0) { throw new InvalidDimensionException(line, startIndex, "Index may not be decimal", index); } return environment.getMemory().getListVariableElementValue(listVariable, (int) indexValue); } @Override public Object visitListExpression(@NotNull TIBasicParser.ListExpressionContext ctx) { List<TIBasicParser.ExpressionContext> expressions = ctx.expression(); List<Complex> evaluatedExpressions = expressions.stream() .map(expression -> ((Value) expression.accept(this)).complex()).collect(Collectors.toList()); return Value.of(evaluatedExpressions); } @Override public Value visitListValue(@NotNull TIBasicParser.ListValueContext ctx) { if (ctx.listVariable() != null) { ListVariable listVariable = (ListVariable) ctx.listVariable().accept(this); return environment.getMemory().getListVariableValue(listVariable); } else if (ctx.listExpression() != null) { return (Value) ctx.listExpression().accept(this); } throw new UnsupportedOperationException("This shouldn't happen"); } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public ListVariable visitListVariable(@NotNull TIBasicParser.ListVariableContext ctx) { String variableName = ctx.listIdentifier().getText(); return ListVariable.fromName(variableName); } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Parameter visitListVariableParameter(@NotNull TIBasicParser.ListVariableParameterContext ctx) { ListVariable var = (ListVariable) ctx.listVariable().accept(this); return Parameter.variable(var, environment.getMemory()); } @Override public Value visitNumber(@NotNull TIBasicParser.NumberContext ctx) { return ContextUtils.extractValueFromNumberContext(ctx); } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Object visitNumberExpression(@NotNull TIBasicParser.NumberExpressionContext ctx) { return ctx.number().accept(this); } @Override public Variables.NumberVariable visitNumericalVariable(@NotNull TIBasicParser.NumericalVariableContext ctx) { String variableName = ctx.getText(); return Variables.resolveNumberVariable(variableName); } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Object visitNumericalVariableExpression(@NotNull TIBasicParser.NumericalVariableExpressionContext ctx) { Variables.NumberVariable variable = (Variables.NumberVariable) ctx.numericalVariable().accept(this); return environment.getMemory().getNumberVariableValue(variable); } @Override public Parameter visitNumericalVariableParameter(@NotNull TIBasicParser.NumericalVariableParameterContext ctx) { Variables.NumberVariable var = (Variables.NumberVariable) ctx.numericalVariable().accept(this); return Parameter.variable(var, environment.getMemory()); } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Parameter[] visitParameterList(@NotNull TIBasicParser.ParameterListContext ctx) { Parameter[] parameterList = new Parameter[ctx.parameter().size()]; List<TIBasicParser.ParameterContext> expression = ctx.parameter(); for (int i = 0; i < expression.size(); i++) { TIBasicParser.ParameterContext parameterContext = ctx.parameter().get(i); parameterList[i] = (Parameter) parameterContext.accept(this); } return parameterList; } @Override public Object visitProgram(@NotNull TIBasicParser.ProgramContext ctx) { return super.visitProgram(ctx); } @Override public ControlFlowElement visitRepeatStatement(@NotNull TIBasicParser.RepeatStatementContext ctx) { final int line = ctx.REPEAT().getSymbol().getLine(); final int startIndex = ctx.REPEAT().getSymbol().getCharPositionInLine(); // No evaluation needed on repeat visit! return new ControlFlowElement(line, startIndex, ControlFlowElement.ControlFlowToken.REPEAT, true, true); } @Override public Optional<Value> visitStatement(@NotNull TIBasicParser.StatementContext ctx) { return Optional.ofNullable((Value) super.visitStatement(ctx)); } @Override public Object visitStopStatement(@NotNull TIBasicParser.StopStatementContext ctx) { throw new TIStopException(ctx.STOP().getSymbol().getLine(), ctx.STOP().getSymbol().getCharPositionInLine()); } @Override public Value visitStoreListDimensionStatement(@NotNull TIBasicParser.StoreListDimensionStatementContext ctx) { int line = ctx.STORE().getSymbol().getLine(); int startIndex = ctx.STORE().getSymbol().getCharPositionInLine(); Value newDimension = (Value) ctx.expression().accept(this); ListVariable listVariable = (ListVariable) ctx.listVariable().accept(this); double dimensionValue = newDimension.complex().getReal(); if (newDimension.hasImaginaryValue()) { throw new InvalidDimensionException(line, startIndex, "Index may not be imaginary", newDimension); } if (dimensionValue % 1 != 0) { throw new InvalidDimensionException(line, startIndex, "Index may not be decimal", newDimension); } environment.getWritableMemory().setListVariableSize(listVariable, (int) dimensionValue); return newDimension; } @Override public Object visitStoreListElementStatement(@NotNull TIBasicParser.StoreListElementStatementContext ctx) { int line = ctx.LEFT_PARENTHESIS().getSymbol().getLine(); int startIndex = ctx.LEFT_PARENTHESIS().getSymbol().getStartIndex(); ListVariable listVariable = (ListVariable) ctx.listVariable().accept(this); Value newValue = (Value) ctx.expression(0).accept(this); Value index = (Value) ctx.expression(1).accept(this); double indexValue = index.complex().getReal(); if (index.hasImaginaryValue()) { throw new InvalidDimensionException(line, startIndex, "Index may not be imaginary", index); } if (indexValue % 1 != 0) { throw new InvalidDimensionException(line, startIndex, "Index may not be decimal", index); } environment.getWritableMemory().setListVariableElementValue(listVariable, (int) indexValue, newValue); return newValue; } @Override public Value visitStoreListStatement(@NotNull TIBasicParser.StoreListStatementContext ctx) { ListVariable variable = (ListVariable) ctx.listVariable().accept(this); Value value = (Value) ctx.expression().accept(this); environment.getWritableMemory().setListVariableValue(variable, value); return value; } @Override public Value visitStoreNumberStatement(@NotNull TIBasicParser.StoreNumberStatementContext ctx) { String variableName = ctx.numericalVariable().getText(); Value value = (Value) ctx.expression().accept(this); Variables.NumberVariable targetVariable = Variables.resolveNumberVariable(variableName); environment.getWritableMemory().setNumberVariableValue(targetVariable, value); return value; } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Value visitStoreStringStatement(@NotNull TIBasicParser.StoreStringStatementContext ctx) { String variableName = ctx.STRING_VARIABLE().getText(); Variables.StringVariable stringVariable = Variables.resolveStringVariable(variableName); Value value = (Value) ctx.expression().accept(this); environment.getWritableMemory().setStringVariableValue(stringVariable, value); return value; } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Object visitStringExpression(@NotNull TIBasicParser.StringExpressionContext ctx) { String text = ctx.STRING().getText(); String stringValue = StringUtils.remove(text, "\""); return Value.of(stringValue); } /** * {@inheritDoc} * <p/> * <p>The default implementation returns the result of calling * {@link #visitChildren} on {@code ctx}.</p> * * @param ctx */ @Override public Value visitStringVariableExpression(@NotNull TIBasicParser.StringVariableExpressionContext ctx) { String variableName = ctx.STRING_VARIABLE().getText(); Variables.StringVariable stringVariable = Variables.resolveStringVariable(variableName); return environment.getMemory().getStringVariableValue(stringVariable); } @Override public ControlFlowElement visitThenStatement(@NotNull TIBasicParser.ThenStatementContext ctx) { int line = ctx.THEN().getSymbol().getLine(); int startIndex = ctx.THEN().getSymbol().getCharPositionInLine(); return new ControlFlowElement(line, startIndex, ControlFlowElement.ControlFlowToken.THEN, false, false); } @Override public ControlFlowElement visitWhileStatement(@NotNull TIBasicParser.WhileStatementContext ctx) { final int line = ctx.WHILE().getSymbol().getLine(); final int startIndex = ctx.WHILE().getSymbol().getCharPositionInLine(); Value value = (Value) ctx.expression().accept(this); boolean lastEvaluation = value.bool(); return new ControlFlowElement(line, startIndex, ControlFlowElement.ControlFlowToken.WHILE, lastEvaluation, true); } private int internalHandleControlFlowLogic(int commandIndex, List<TIBasicParser.CommandContext> commandList, Stack<ControlFlowElement> flowElementStack, Stack<ControlFlowElement.ControlFlowToken> skipCommandsStack, TIBasicParser.CommandContext nextCommand) { int commandListSize = commandList.size(); ControlFlowElement currentFlowElement = (ControlFlowElement) nextCommand.accept(this); ControlFlowElement topFlowElement; if (!flowElementStack.empty()) { topFlowElement = flowElementStack.peek(); } else { topFlowElement = null; } // Check if current token depends on a certain pre-token final int line = currentFlowElement.getLine(); final int charIndex = currentFlowElement.getCharIndex(); switch (currentFlowElement.getToken()) { case INCREMENT_SKIP_GREATER: case DECREMENT_SKIP_LESS: if (commandIndex + 1 >= commandList.size()) { throw new IllegalControlFlowException(line, charIndex, "Missing next command"); } if (!currentFlowElement.getLastEvaluation()) { LOGGER.debug("Skipping next command..."); commandIndex++; } break; case GOTO: JumpingControlFlowElement jumpElement = (JumpingControlFlowElement) currentFlowElement; String targetLabel = jumpElement.getTargetLabel(); commandIndex = environment.getProgramStack().peek().getLabelJumpTarget(jumpElement.getTargetLabel()); LOGGER.debug("Jumping to label {} at command {}", targetLabel, commandIndex); break; case LABEL: break; // Do nothing when encountering Label case FOR: boolean isFirstIteration = false; if ((topFlowElement == null || topFlowElement.getCommandIndex() != commandIndex)) { // Set start value (should be executed ALWAYS when this block is executed the *first* time from top-down TIBasicParser.ForStatementContext forStatementContext = commandList.get(commandIndex) .controlFlowStatement().forStatement(); String variableName = forStatementContext.numericalVariable().getText(); Value value = (Value) forStatementContext.expression(0).accept(this); Variables.NumberVariable targetVariable = Variables.resolveNumberVariable(variableName); environment.getWritableMemory().setNumberVariableValue(targetVariable, value); isFirstIteration = true; } if (currentFlowElement.isRepeatable() && (currentFlowElement.getLastEvaluation() || isFirstIteration)) { if (isFirstIteration) { currentFlowElement.setLastEvaluation(true); // Hack for making sure, that the first increment is ALWAYS done at the end LOGGER.debug("Entering FOR loop at command {}", commandIndex); } else { LOGGER.debug("Continuing FOR loop at command {}", commandIndex); flowElementStack.pop(); } currentFlowElement.setCommandIndex(commandIndex); flowElementStack.push(currentFlowElement); } else { if (topFlowElement != null && topFlowElement.getCommandIndex() == commandIndex) { flowElementStack.pop(); } LOGGER.debug("Skipping commands until next END from FOR command {}", commandIndex); skipCommandsStack.push(ControlFlowElement.ControlFlowToken.FOR); } break; case REPEAT: currentFlowElement.setCommandIndex(commandIndex); LOGGER.debug("Entering repeat loop at command {}", commandIndex); flowElementStack.push(currentFlowElement); break; case WHILE: if (!currentFlowElement.getLastEvaluation()) { LOGGER.debug("Skipping commands until next END from WHILE command {}", commandIndex); skipCommandsStack.push(ControlFlowElement.ControlFlowToken.WHILE); } else { currentFlowElement.setCommandIndex(commandIndex); flowElementStack.push(currentFlowElement); } break; case THEN: if (topFlowElement == null) { throw new IllegalControlFlowException(line, charIndex, "Illegal 'Then' Statement"); } if (topFlowElement.getToken() != ControlFlowElement.ControlFlowToken.IF) { throw new IllegalControlFlowException(line, charIndex, "Illegal 'Then' Statement without preceding 'If'"); } if (topFlowElement.getLastEvaluation()) { currentFlowElement.setLastEvaluation(true); } else { currentFlowElement.setLastEvaluation(false); skipCommandsStack.push(ControlFlowElement.ControlFlowToken.ELSE); LOGGER.debug("Skipping commands until next ELSE from THEN command {}", commandIndex); } flowElementStack.push(currentFlowElement); break; case ELSE: if (topFlowElement == null) { throw new IllegalControlFlowException(line, charIndex, "Illegal 'Else' Statement"); } if (topFlowElement.getToken() != ControlFlowElement.ControlFlowToken.THEN) { throw new IllegalControlFlowException(line, charIndex, "Illegal 'Else' Statement without preceding 'Then'"); } if (topFlowElement.getLastEvaluation()) { // Skip until next "END" if previous if was true skipCommandsStack.push(ControlFlowElement.ControlFlowToken.END); LOGGER.debug("Skipping commands until next END from ELSE command {}", commandIndex); } break; case END: if (topFlowElement == null) { throw new IllegalControlFlowException(line, charIndex, "Illegal 'End' Statement without preceding endable element"); } if (topFlowElement.getToken() == ControlFlowElement.ControlFlowToken.REPEAT) { // Repeat will only be check at the END command! final TIBasicParser.CommandContext commandContext = commandList .get(topFlowElement.getCommandIndex()); Value v = (Value) commandContext.controlFlowStatement().repeatStatement().expression().accept(this); if (v.bool()) { topFlowElement.setRepeatable(false); } else { topFlowElement.setRepeatable(true); } } else if (topFlowElement.getToken() == ControlFlowElement.ControlFlowToken.FOR) { if (topFlowElement.getLastEvaluation()) { topFlowElement.setRepeatable(true); TIBasicParser.ForStatementContext forStatementContext = commandList .get(topFlowElement.getCommandIndex()).controlFlowStatement().forStatement(); String variableName = forStatementContext.numericalVariable().getText(); Value increment; if (forStatementContext.expression().size() == 3) increment = (Value) forStatementContext.expression(2).accept(this); else increment = Value.of(1); Variables.NumberVariable targetVariable = Variables.resolveNumberVariable(variableName); Value value = environment .runRegisteredExpressionFunction("+", environment.getMemory().getNumberVariableValue(targetVariable), increment) .get(); environment.getWritableMemory().setNumberVariableValue(targetVariable, value); flowElementStack.push(topFlowElement); // Push the flow element again -> workaround } else { topFlowElement.setRepeatable(false); } } if (topFlowElement.isRepeatable()) { commandIndex = topFlowElement.getCommandIndex() - 1; // Move counter backwards LOGGER.debug("Moving command counter to index {}", commandIndex); } flowElementStack.pop(); break; case IF: // Look ahead if the next command might be a "Then" i.e. if it is a controlflow statement if (commandListSize <= commandIndex + 1) { throw new IllegalControlFlowException(line, charIndex, "Illegal 'If' at the end of the program"); } else if (commandList.get(commandIndex + 1).isControlFlowStatement) { flowElementStack.push(currentFlowElement); LOGGER.debug("Predicted multiline IF at command {}", commandIndex); } else if (!currentFlowElement.getLastEvaluation()) { // If the next command is not a flow statement and the If evaluated to false, skip the next command (i.e. no else allowed!) commandIndex++; LOGGER.debug("Skipped IF statement without ELSE clause at command {}", commandIndex); } break; default: throw new NotImplementedException("Flow not implemented"); } return commandIndex; } private int internalHandleSkipFlowLogic(int currentCommandCounter, List<TIBasicParser.CommandContext> commandList, Stack<ControlFlowElement.ControlFlowToken> skipCommandsStack, TIBasicParser.CommandContext nextCommand) { int commandListSize = commandList.size(); final String enumName = nextCommand.controlFlowStatement().flowType; ControlFlowElement.ControlFlowToken currentFlowToken = EnumUtils .getEnum(ControlFlowElement.ControlFlowToken.class, enumName); ControlFlowElement.ControlFlowToken topToken = skipCommandsStack.peek(); if (currentFlowToken == null) { throw new IllegalStateException( "Internal error: control flow token is null at command " + currentCommandCounter); } switch (currentFlowToken) { case IF: // Look ahead if the next command might be a "Then" i.e. if it is a controlflow statement if (commandListSize <= currentCommandCounter + 1) { throw new IllegalControlFlowException(-1, -1, "Illegal 'If' at the end of the program"); } else if (commandList.get(currentCommandCounter + 1).isControlFlowStatement) { skipCommandsStack.push(currentFlowToken); LOGGER.debug("Predicted multiline IF while skipping over command {}", currentCommandCounter); } else { LOGGER.debug("Skipping over single line IF at command {}", currentCommandCounter); currentCommandCounter++; } break; case THEN: if (topToken != ControlFlowElement.ControlFlowToken.IF) throw new IllegalControlFlowException(-1, -1, "Illegal 'Then' Statement without preceding 'If'"); skipCommandsStack.pop(); skipCommandsStack.push(currentFlowToken); break; case ELSE: if (skipCommandsStack.size() > 1 && topToken != ControlFlowElement.ControlFlowToken.THEN) throw new IllegalControlFlowException(-1, -1, "Illegal 'Else' Statement without preceding 'Then' "); skipCommandsStack.pop(); if (!skipCommandsStack.empty()) skipCommandsStack.push(topToken); break; case FOR: case WHILE: case REPEAT: skipCommandsStack.push(currentFlowToken); break; case END: skipCommandsStack.pop(); break; case GOTO: case LABEL: break; default: throw new IllegalStateException("Illegal flow token: " + currentFlowToken); } if (skipCommandsStack.empty()) { LOGGER.debug("Skip stack is now empty - continuing execution at command {}", currentCommandCounter + 1); } return currentCommandCounter; } /** * Internal function for processing expressions. This method takes an initial value and both a list of operators * and a list of operands. Each i-th element in the operator list will be applied to the i-1-th and i-th element * in the operand list. Each operator will be looked up in the current environment's command list. * If there is only one operand, the left hand side will be returned. * <p/> * E.g.: operator = ['+','-'] and operands = [1,2,3] will result in 1 + 2 - 3 * * @param operators * List of operators to be applied. Note: the first operator will be applied to the first and second * operand * (see above) * @param contextRules * The child contexts that will be invoked by the visitor. Each context must return an object of type * {@link * Value}. */ @NotNull private Value processGenericExpressions(@NotNull List<String> operators, @NotNull List<? extends RuleContext> contextRules) { Value lhs = (Value) contextRules.get(0).accept(this); for (int i = 1; i < contextRules.size(); i++) { Value rhs = (Value) contextRules.get(i).accept(this); lhs = environment.runRegisteredExpressionFunction(operators.get(i - 1), lhs, rhs).get(); } return lhs; } }