Арифметическое выражение (arith.go)



Составьте программу arith.go, вычисляющую значение скобочного арифметического выражения.



Арифметическое выражение считывается из аргументов командной строки и представляет собой строку, содержащую целые числа от 0 до 2 в 31 степени, знаки четырёх арифметических операций, имена переменных, круглые скобки и пробелы. Имена переменных представляют собой последовательности латинских букв и цифр, начинающиеся с буквы. Арифметические операции — целочисленные, то есть 5/2 даёт 2, а не 2.5. Пробелы не несут никакого смысла и должны игнорироваться программой.



Примеры выражений, которые могут быть поданы на вход программе:



-x * (x+10) * (128/x-5)

Length * Height * Depth




Грамматика для арифметических выражений записывается как



<E> ::= <E> + <T> | <E> - <T> | <T>.

<T> ::= <T> * <F> | <T> / <F> | <F>.

<F> ::= <number> | <var> | ( <E> ) | - <F>.




Здесь number и var обозначают множества терминальных символов, соответствующих числам и именам переменных.



После удаления левой рекурсии получается LL(1)-грамматика



<E> ::= <T> <E'>.

<E'> ::= + <T> <E'> | - <T> <E'> | .

<T> ::= <F> <T'>.

<T'> ::= * <F> <T'> | / <F> <T'> | .

<F> ::= <number> | <var> | ( <E> ) | - <F>.




Алгоритм вычисления значения выражения должен быть разбит на две части: лексический анализатор и синтаксический анализатор.



Задачей лексического анализатора является разбиение арифметического выражения на лексемы, то есть выделение в нём числовых констант, имён переменных, знаков операций и круглых скобок. Пробелы во время лексического анализа пропускаются. Каждая лексема представляется в виде экземпляра структуры Lexem:



type Lexem struct {

Tag

Image string

}


Здесь Image — подстрока арифметического выражения, соответствующая распознанной лексеме, а Tag — целочисленный код, задающий тип лексемы:



type Tag int



const (

ERROR Tag = 1 << iota // Неправильная лексема

NUMBER // Целое число

VAR // Имя переменной

PLUS // Знак +

MINUS // Знак -

MUL // Знак *

DIV // Знак /

LPAREN // Левая круглая скобка

RPAREN // Правая круглая скобка

)




Значения кодов лексем образуют степени двойки, чтобы можно было эффективно проверять принадлежность текущей лексемы некоторому множеству лексем. Например, если lx — переменная, в которой хранится текущая лексема, то проверить, является ли она аддитивной арифметической операцией, можно следующим образом:



if lx.Tag & (PLUS | MINUS) != 0 {

...

}


Лексический анализатор должен выполняться в отдельной go-программе, получающей на вход арифметическое выражение и канал лексем и отправляющей распознанные лексемы в этот канал:



func lexer(expr string, lexems chan Lexem) {

...

}


Синтаксический анализатор должен быть составлен методом рекурсивного спуска по приведённой LL(1)-грамматике.



Программа должна запрашивать у пользователя значения переменных, входящих в выражение, и выводить в стандартный поток вывода значение выражения или сообщение «error», если выражение содержит синтаксическую ошибку. Если некоторая переменная входит в выражение многократно, её значение всё равно должно запрашиваться только один раз.



@golang_interview