Если проблема заключается только в выражении, включающем монадические и двоичные операторы, круглые скобки, значения переменных и постоянные операнды, вы можете написать анализатор / оценщик рекурсивного спуска, который будет одновременно анализировать и оценивать выражение. Не нужно строить деревья или ИЛ или ...
[Если вам нужно кодировать более сложный синтаксис, такой как операторы и методы, вам понадобится более сложный синтаксический анализатор, и тогда генератор парсера окупится]
Просто для выражения вы можете закодировать анализатор рекурсивного спуска непосредственно из BNF для вашего выражения. Убедись, что ты
первый левый фактор - общность для каждого правила, например, не
SUM = TERM '+' TERM ;
SUM = TERM '-' TERM ;
но
SUM = TERM ( '+' TERM | '-' TERM ) ;
Для каждого нетерминала левой стороны создайте (возможно, рекурсивную) подпрограмму, содержащую индекс в строку для анализа, которая возвращает либо значение выражения (предположим, с плавающей точкой), либо выдает (синтаксическую) ошибку. Для каждого правого токена вы кодируете тест; если токен отсутствует, он передает управление альтернативе или выдает синтаксическую ошибку, если нет других альтернатив. Если токен присутствует, он оценивает токен: если значение терминала (например, число или переменное значение), получает значение и продвигает ввод; если нетерминал, вызывает его, чтобы увидеть, присутствует ли этот синтаксис; если оператор, просто игнорирует его, но продвигает ввод. Если ваши тесты соответствуют всем элементам правой части, вычислите результат выражения и верните его.
Так что для грамматики:
EXP = SUM ;
SUM = TERM ( '+' TERM | '-' TERM ) ;
TERM = PRIMARY ( '*' PRIMARY | '/' PRIMARY ) ;
PRIMARY = '-' PRIMARY | '(' EXP ')' | NUMBER | VARIABLE ;
И дан буфер символов INPUT, содержащий выражение,
и глобальная переменная I индекс в INPUT, код примерно:
float EXP()
{ return SUM();
}
float SUM()
{ float t=TERM();
if MATCH("+") return t+TERM();
if MATCH("-") return t-TERM();
throw SYNTAXERROR;
}
float TERM()
{ float t= PRIMARY();
if MATCH("*") return t*PRIMARY();
if MATCH("/") return t/PRIMARY();
throw SYNTAXERROR;
}
float PRIMARY()
{ float t;
if MATCH("-") return -PRIMARY();
if MATCH("(")
{ t=EXP();
if MATCH(")") return t;
else throw SYNTAXERROR
}
try t=NUMBER();
catch SYNTAXERROR
return VARIABLE();
endtry
}
float NUMBER() // simple float input conversion
{ float t=0;
fractiondigits=0;
exponent=0;
switch INPUT(I)
{ case "0".."9":
{ t=t*10+INPUT(I)-"0"; I++;
while ISDIGIT(INPUT(I))
{ t=t*10+INPUT(I)-"0"; I++ }
if MATCH(".")
goto collect_fraction;
else goto collect_exponent
}
case ".": goto collect_fraction;
default: throw SYNTAXERROR
}
collect_fraction:
while ISDIGIT(INPUT(I))
{ t=t*10+INPUT(I)-"0"; I++; fraction_digits++; }
collect_exponent:
if MATCH("E")
{ sign=false;
if MATCH("-") sign=true;
if !ISDIGIT(INPUT(I)) throw SYNTAXERROR;
while ISDIGIT(INPUT(I))
{ exponent=exponent*10+INPUT(I)-"0"; I++; }
if sign=true then exponenent=-exponent;
}
return t*10^(exponent-fractiondigits);
}
float VARIABLE() // handles single letter variable names.
{ if ISLETTER(INPUT(I))
{ I++;
return VARIABLEVALUE[INPUT(I)-"A"]
}
else throw SYNTAXERROR
}
boolean MATCH(c: char)
{ if INPUT(I)==c
{ I++;
return true;
}
else return false
Вы, очевидно, захотите написать грамматику для выражений, которые вы хотите оценить. Но при условии, что вы добавляете только реляционные операторы и операторы AND, OR и NOT, следуя этому стилю, вам потребуется около 30 минут, чтобы все это кодировать.
Это не учитывает, как входное выражение было собрано в буфере INPUT, и не учитывает вопрос о том, как переменные получают значения; Я предположил, что карта имен переменных к значениям была каким-то волшебным образом заполнена заранее. Если вы хотите разрешить простые назначения, а также выражения, просто немного увеличьте BNF, добавив правило, разрешающее назначения, например,
EXP = VARIABLE ':=' EXP ;
Обработка присваивания потребует некоторой хитрости: когда вы сопоставляете фрагменты и обнаруживаете VARIABLE, вам понадобится способ получить имя переменной (измените VARAIBLE, чтобы запомнить имя переменной в глобальном), и где синтаксис правила назначения распознан, обновите карту имен переменных до собранного значения.
Входной код с плавающей точкой является хаком и может выдавать немного некорректные входные значения (но это было легко кодировать :). Если вы хотите более точные преобразования входных данных с плавающей точкой, вам просто нужно собрать символы, составляющие константу с плавающей точкой, и затем передайте их в библиотечную процедуру преобразования строки в плавающее число.