2015-10-29 7 views
0

Я использую алгоритм Shunting-Yard (https://en.wikipedia.org/wiki/Shunting-yard_algorithm) в программе Java, чтобы создать калькулятор. Я почти готов, но мне все еще нужно выполнять функции. Я столкнулся с проблемой: я хочу, чтобы калькулятор автоматически умножал переменные, такие как x и y при объединении. Пример: калькулятор преобразует xy в x * y. Кроме того, я хочу, чтобы калькулятор преобразовал (x) (y) в (x) * (y) и x (y) в x * (y). Я сделал все это, используя следующий код:Функции шунтирования-ярда

infix = infix.replaceAll("([a-zA-Z])([a-zA-Z])", "$1*$2"); 
infix = infix.replaceAll("([a-zA-Z])\\(", "$1*("); 
infix = infix.replaceAll("\\)\\(", ")*("); 
infix = infix.replaceAll("\\)([a-zA-Z])", ")*$1"); 

(. В моем калькуляторе, имена переменных всегда одиночные символы)
Это сейчас отлично работает, но когда я реализую функции это будет, конечно, не работа. Он превратит «sin (1)» в «s * i * n * (1)». Как я могу сделать этот код преобразованием умножения только для операторов, а не для функций?

ответ

3

Предварительная обработка ввода для разбора не является хорошим способом реализовать то, что вы хотите. Замена текста не может знать, что знает алгоритм синтаксического анализа, а также потерять исходный ввод, который может быть полезен для печати полезных сообщений об ошибках.

Вместо этого вы должны решить, что делать в соответствии с контекстом. Сохраните тип ранее разобранного токена с специальным типом для начала ввода.

Если предыдущий токен был значением токена –, то число, имя переменной или закрывающая скобка подэкстента –, а текущий - токен значения, также выдают дополнительный оператор умножения.

Та же логика может быть использована для определения того, является ли знак минус унарным отрицанием или двоичным вычитанием: это вычитание, если минус найден после токена значения и отрицания иначе.

Ваша идея конвертировать x(y) в x * (y), конечно, столкнется с синтаксисом вызова функции.

0

Мы можем разбить его на две части. Существует одно правило для выражений в квадратных скобках и другое для умножений.

В отличие от статьи в Википедии, которая преднамеренно упрощена для пояснения, я хотел бы привести более подробный пример, например Parsing Expressions by Recursive Descent, который касается выражений в квадратных скобках.

Это код, который я использую для своего парсера, который может работать с неявным умножением. У меня есть многобуквенные имена переменных и используйте пробел для разделения разных переменных, чтобы вы могли иметь «2 pi r».

protected void expression() throws ParseException { 
    prefixSuffix(); 
    Token t = it.peekNext(); 
    while(t!=null) { 
     if(t.isBinary()) { 
      pushOp(t); 
      it.consume(); 
      prefixSuffix(); 
     } 
     else if(t.isImplicitMulRhs()) { 
      pushOp(implicitMul); 
      prefixSuffix(); 
     } 
     else 
      break; 
     t=it.peekNext(); 
    } 
    while(!sentinel.equals(ops.peek())) { 
     popOp(); 
    } 
} 

Для этого требуется несколько других функций.

Я использовал отдельный шаг токенизации, который разбивает входные данные на дискретные маркеры. Класс Tokens имеет ряд методов, в частности Token.isBinary(), если оператор является двоичным оператором типа +, =, *, /. Другой метод: Token.isImplicitMulRhs() проверяет, может ли токен отображаться с правой стороны неявного умножения, это будет верно для чисел, имен переменных и левых скобок.

Для входного потока используется Iterator<Token>. it.peekNext() смотрит на следующий токен и it.consume() переходит к следующему токену на входе.

pushOp(Token) толкает токен на стек оператора, а popOp удаляет один и.У pushOp есть логика для обработки приоритета разных операторов. Выталкивание оператора, если они имеют более низкий приоритет

protected void pushOp(Token op) 
{ 
    while(compareOps(ops.peek(),op)) 
     popOp(); 
    ops.push(op); 
} 

Особо следует отметить implicitMul искусственный маркер с тем же приоритетом, что и умножение, которое натягивается на стек оператора.

prefixSuffix() обрабатывает выражения, которые могут быть числами и переменными с необязательным префиксом операторов суффиксов. Это распознает «2», «x», «-2», «x ++», удаляя маркеры с входа и добавляя их соответственно в стек вывода/оператора.

Мы можем думать об этой процедуре в BNF, как

<expression> ::= 
      <prefixSuffix> (<binaryOp> <prefixSuffix>)* // normal binary ops x+y 
      | <prefixSuffix> (<prefixSuffix>)* // implicit multiplication x y 

Обработка скобок делается в prefixSuffix(). Если это обнаружит левый кронштейн, он затем рекурсивно вызовет expression(). Чтобы обнаружить подходящую правую скобку, в стек оператора помещается специальный сигнальный токен. Когда на входе встречается правая скобка, основной цикл прерывается, и все операторы в стеке оператора выскакивают до тех пор, пока не появится столбец, и управление вернется к prefixSuffix(). Код для этого может быть как

void prefixSuffix() { 
    Token t = it.peekNext(); 
    if(t.equals('(')) { 
     it.consume(); // advance the input 
     operatorStack.push(sentinel); 
     expression(); // parse until ')' encountered 
     t = it.peekNext(); 
     if(t.equals(')')) { 
      it.consume(); // advance the input 
      return; 
     } else throw Exception("Unmatched ("); 
    } 
    // handle variable names, numbers etc 
} 
0

Другой подход может быть использование лексем, аналогично тому, как синтаксического анализа работы.

Первым этапом будет преобразование входного текста в список токенов, которые представляют собой объекты, которые представляют собой как тип найденной сущности, так и ее значение. Например, у вас может быть переменный токен, его значение является именем переменной («x», «y» и т. Д.), Токеном для открытой или закрывающей скобки и т. Д. Поскольку, я полагаю, вы знаете заблаговременно имена функций, которые могут быть использованы калькулятором, у вас также будет функционировать токен, его значение будет именем функции. Таким образом, выход фазы токенизации различает переменные и функции.

Реализация этого не слишком сложна, всегда старайтесь сначала совместить имена функций, , поэтому «sin» будет распознаваться как функция, а не как три переменные.

Теперь второй этап может состоять в том, чтобы вставить отсутствующие операторы умножения. Это не будет трудно сейчас, так как вы знаете, что вы просто вставить их между:

{VAR, RIGHT_PAREN} и {VAR, LEFT_PAREN, ФУНКЦИИ}

Но никогда между функцией и LEFT_PAREN.