/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.icepdf.core.pobjects.functions.postscript; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.Stack; /** * A state machine used to parse valid type 4 functions tokens in a input * stream. As the tokens are parsed found operands are executed to mananipulate * the stack. * * @author ICEsoft Technologies Inc. * @since 4.2 */ public class Lexer { // stream reader pointers. private Reader reader; private char[] buf = new char[2056]; private int pos = 0, numRead = 0, startTokenPos = 0; private int tokenType = 0; // expression depth count used to properly differ if and elseif operands. private int expressionDepth; // lexer states private static final int TOKEN_NUMBER = 1, TOKEN_OPERAND = 2, TOKEN_EXPRESSION = 3, TOKEN_BOOLEAN = 5; // procedure isa any {expression...} private Procedure procedures; private Procedure currentProcedure; public Lexer() { procedures = new Procedure(null); } /** * Type 4 function input stream to pars.e * * @param in type 4 function input stream. */ public void setInputStream(InputStream in) { setReader(new InputStreamReader(in)); } protected void setReader(Reader reader) { this.reader = reader; } /** * Parse the input stream associated with this instance. * * @param input array of 1 or more numbers to be pushed onto the stack before * the type 4 function is executed. * @throws IOException if the input stream is null or incomplete. */ public void parse(float[] input) throws IOException { if (reader == null) { throw new IOException("Type 4 function, null input stream reader."); } // set current procedure which is the root {}. currentProcedure = procedures; // push input values on the stack for (Number num : input) { currentProcedure.getProc().push(num); } tokenType = TOKEN_EXPRESSION; boolean done = false; while (!done) { // Did we reach the end of the buffer, if so copy the next block // of data into the buffer. if (pos == buf.length) { // Copy the start of the token to the beginning System.arraycopy(buf, startTokenPos, buf, 0, pos - startTokenPos); pos = buf.length - startTokenPos; startTokenPos = 0; numRead = pos; } // Read at pos position int n = reader.read(buf, pos, buf.length - pos); if (n <= 0) break; numRead += n; // Scan to the numRead while (pos < numRead) { if (tokenType == TOKEN_NUMBER) { numberStart(); } else if (tokenType == TOKEN_OPERAND) { operandStart(); } else if (tokenType == TOKEN_BOOLEAN) { booleanStart(); } else if (tokenType == TOKEN_EXPRESSION) { expressionStart(); } } } } /** * Gets the stack associated with this lexer. Once parse has successfully * executed the stack will contain n numbers which represent the type 4 * function output. * * @return stack containing the output of the type 4 function. If #parse() * was not called the stack will be empty */ public Stack getStack() { return procedures.getProc(); } /** * Utility to find the next token state. */ private void parseNextState() { while (pos < numRead) { if (!(buf[pos] == ' ' || buf[pos] == '\t' || buf[pos] == '\n' || buf[pos] == '\r')) { break; } pos++; } // We found the end if (pos < numRead) { startTokenPos = pos; // look for number tokens. if (buf[pos] < 'A') { tokenType = TOKEN_NUMBER; } // else we have a boolean or operand. else { // look for a boolean if ((buf[pos] == 'f' && buf[pos + 1] == 'a') || (buf[pos] == 't' && buf[pos + 3] == 'e')) { tokenType = TOKEN_BOOLEAN; } // otherwise we have an operand. else if (buf[pos] < '{') { tokenType = TOKEN_OPERAND; } // special expression or procedure definition else if (buf[pos] == '{' || buf[pos] == '}') { tokenType = TOKEN_EXPRESSION; } else { parseNextState(); } } } } /** * Utility to find an expression {some opps}. We always ignore the first * as it is the start of the function but all other will be assoicated with * a if or elseif operand and as a result we don't eval the containing * operands until the if or elseif operand is encountered. */ private void expressionStart() { while (pos < numRead) { // need to revisit the logic here, seems overly complicated. if (!(buf[pos] == '{' || buf[pos] == '}')) { break; } // corner case, no space between '}{' in {exp}{exp} if (pos + 1 < numRead && buf[pos] == '}' && buf[pos + 1] == '{') { pos++; break; } pos++; } if (pos < numRead) { Operator operand = OperatorFactory.getOperator(buf, startTokenPos, pos - startTokenPos); // found a start if (operand.getType() == OperatorNames.OP_EXP_START) { expressionDepth++; if (expressionDepth > 1) { currentProcedure = new Procedure(currentProcedure); } } // found '}' so we decrement our depth count. if (operand.getType() == OperatorNames.OP_EXP_END) { currentProcedure = currentProcedure.getPrevious(); expressionDepth--; } } // go baby go! parseNextState(); } /** * Utility for processing the operand state. */ private void operandStart() { startTokenPos = pos; while (pos < numRead) { if (isDelimiter(buf[pos])) { break; } pos++; } if (pos < numRead && pos > startTokenPos) { Operator operand = OperatorFactory.getOperator(buf, startTokenPos, pos - startTokenPos); // execute differed execution by looking at expression depth. if (expressionDepth > 1) { currentProcedure.getProc().push(operand); } else { // execute the operand operand.eval(currentProcedure.getProc()); } } parseNextState(); } /** * Utility of processing a number state. */ private void numberStart() { startTokenPos = pos; while (pos < numRead) { if (isDelimiter(buf[pos])) { break; } pos++; } if (pos < numRead) { // push the number currentProcedure.getProc().push(Float.parseFloat(new String(buf, startTokenPos, pos - startTokenPos))); } parseNextState(); } /** * Utility for processing boolean */ private void booleanStart() { while (pos < numRead) { if (isDelimiter(buf[pos])) { break; } pos++; } if (pos < numRead) { currentProcedure.getProc().push(Boolean.valueOf(new String(buf, startTokenPos, pos - startTokenPos))); } parseNextState(); } /** * Utility for finding token delimiter in a type 4 function stream. * * @param c character to compare against known delimiters. * @return true if c is a delimiter otherwise, false. */ private static boolean isDelimiter(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '{' || c == '}'; } }