Стоит обратить внимание на то, что в коде присутствуют токены начинающиеся с "U" (Uncalculated -невычисляемый), при этом они имеют зеркальные токены без такой приставки. Например, BOOLFUNC, U_BOOLFUNC. Позднее в парсере выражений - невычисляемые токены остаются такими, какими были, а вычисляемые вычисляются. Все парсеры между собой отличаются только перечислением функций которые нужно вычсислять и какие нужно оставить для более позднего вычисления.
Например в коде парсера колонок (column.jison) это выглядит - так:
Все булевые функции, которые зависят от рядов - остаются, другие - вычисляются сразу.
Крайние случаи parser.jison и calculator.jison: если в первом все функции и переменные невычисляемые, то во втором невычисляемых - нет.
Парсер выражений
Program: RESULT EOF {return $1; };/* RETURN RESULT */RESULT: MATH -> $1;| U_MATH -> $1;;/* NUMBERS */NUMERIC: ZERO -> 0;|'('NUMBER')' -> $2; | NUMBER -> Number($1);| CONSTANTA -> Number(CONTEXT[$1]);| FUNC '(' ARGS ')' -> Number(LIB[$FUNC]($ARGS));;ZERO: ZNUMBER -> $1;| ZERO '-''-' MATH -> 0+Number($4);| MATH '-''-' ZERO -> Number($1)+0; | U_MATH '*' ZERO -> $3;| ZERO '*' U_MATH -> $1;| U_MATH '/' ZERO -> $3;| ZERO '/' U_MATH -> $1;| MATH '*' ZERO -> $3;| ZERO '*' MATH -> $3;| MATH '/' ZERO -> $3;| ZERO '/' MATH -> $1;| ZERO '/' ZERO -> $1;| ZERO '*' ZERO -> $1;| ZERO '+' ZERO -> $1;| ZERO '-' ZERO -> $1;|'('ZERO')' -> $2; ;MATH: NUMERIC -> $1;| IF_THEN -> $1;| IF_THEN_ELSE -> $1;| SWITCH -> $1; | MATH '+' MATH -> Number($1)+Number($3); | MATH '-' MATH -> Number($1)-Number($3);| MATH '*' MATH -> Number($1)*Number($3);| MATH '/' MATH -> Number($1)/Number($3);| MATH '+' ZERO -> Number($1);| ZERO '+' MATH -> Number($3);| MATH '-' ZERO -> Number($1);| ZERO '-' MATH -> 0-Number($3);|'-' MATH %prec UMINUS -> -Number($2);|'('MATH')' -> $2; ;/* Весь блок можно посмотреть в исходниках системы */
В данном блоке стоит обратить внимание на то, что модуль имеет отдельную структуру для работы с "0". С одной стороны это особенность, что деление на ноль здесь будет не бесконечность, а ноль. С другой стороны, в коде прописаны ситуации когда реализация простых правил ("что угодно" умножить на ноль = получается ноль и т.д.) позволяет значительно сократить объемы вычислений (не вычислять это "что угодно")
Как происходит вычисление: расмотрим простой пример 2+2*2
Поскольку мы не описали правил для сложений или умножения 2-х типов Number, то их тип повышается постепенно до Numeric и Math (на уровне Math арифметические операции мы уже описали)
Почему произошло сначала умножение, а потом сложение - потому, что мы это описали в правилах приоритетов вычислений:
%left '+''-'%left '*''/'%left '^'%left UMINUS%left OR%left AND%left NOT
В этом модуле прописано, как ведет себя if, if...else, switch и прочии конструкции, но в целом - ничего интересного нет.
Несколько слов по копиляции парсеров. После того, как были внесены изменения в код jison парсеров, необходимо запустить консольную админку (node admin.js) и выбрать пункт компиляция парсеров
Окончание процесса компиляции сопровождается запуском mocha-тестов по проверке работы парсера
Сами тесты находятся в JSON файле ./classes/calculator/jison/mocha/Documents/blank.js и имеют следующий формат:
{ Formula:"if ( ( ismonth and not periodin( 11 ) ) or ( periodin (42,43,44,46,444,446,416,442) ) ,{1},{2})", Contexts:[ {Context:{ismonth:true,period:19},Result :'1'}, {Context:{ismonth:false,period:442},Result :'1'}, {Context:{ismonth:true,period:11},Result :'2'} ]}