При внесении изменений в
константные массивы из элементов CHAR, вдруг обнаружилась странная особенность Оберона, на которую до сих пор не обращали внимания - приоритет операции "унарный минус".
Унарный минус в руководствах, вообще, упомянут вскользь, его приоритет не задан. Есть вычитание с приоритетом аддитивных операций, в описании вычитания сказано "Если используются одноместные операции, - обозначает перемену знака". Поэтому во всех Оберонах, начиная с самого первого, операция "унарный минус" имеет приоритет как у аддитивной операции.
Получается, что
(-1 MOD 256) означает "
-(1 MOD 256)" , а не
"(-1) MOD 256". Кроме того, "-1" это не литерал "минус единица", а константное выражение "изменение знака у литерала 1", в результате вычисления которого получается константа "минус 1". Если бы унарный минус имел наивысший приоритет, то не было бы практической разницы для записи "-1" (и литерал и константное выражение означало бы "минус единица"). Но если приоритет унарного минуса ниже мультипликативных операций, то разница есть:
(-1 MOD 256) = -1, а выражение "
(-1) MOD 256" дает 255.
Выражение "
-1 MOD 256" я бы прочитал как "минус единица, взятая по модулю 256", а в Обероне это "изменение знака у единицы, взятой по модулю 256". Графически знак "-" занимает меньшую площадь, чем слово MOD, поэтому зрительно "связывает сильнее", т.е. из соображений наглядности "сначала минус, а потом MOD". Числа бывают положительные и отрицательные, "-" воспринимается как часть числа, а не как операция. Но почему-то сделано наоборот. И это коварная ловушка, в которую вляпаются новички.
Много коварство таит Си, но там унарный минус сделан лучше. В Паскале, Си, Бейсике (по крайней мере, в большинстве диалектов) и многих других языках унарный минус имеет наивысший приоритет (во всяком случае, выше мультипликативных операций).
Почему же в Обероне не так? На форуме
oberoncore.ru ясных ответов на этот вопрос пока не дали. В личной переписке с Zorko мы решили было, что приоритет унарного минуса понизили до такого как у аддитивных операций исключительно чтобы упростить: a) описание языка; b) компилятор.
Но оказалось, сменить приоритет очень легко. Сейчас формы Бэкуса-Наура для выражения такие:
Код: "OBERON"
Expression = SimpleExpression [Relation SimpleExpression]
SimpleExpression = ["+" | "−"] Term {AddOperator Term}
Term = Factor {MulOperator Factor}
Factor = Designator [ActualParameters] | number | character |
string | NIL | Set | "(" Expression ")" | "˜" Factor
Set = "{" [Element {"," Element}] "}"
Element = Expression [".." Expression]
ActualParameters = "(" [ExpressionList] ")"
Relation = "=" | "#" | "<" | "<=" | ">" | ">=" | IN | IS
AddOperator = "+" | "−" | OR
MulOperator = "*" | "/" | DIV | MOD | "&"
Для изменения приоритета унарного минуса (и плюса, заодно), достаточно изменить 2 правила:
Код: "OBERON"
SimpleExpression = Term {AddOperator Term}
Factor = Designator [ActualParameters] | number | character |
string | NIL | Set | "(" Expression ")" |("˜" |"+" | "−") Factor
Из SimpleExpression убрать, в Factor поставить - сложнее не стало.
Изменить разбор выражений по этой новой грамматике в модуле OPP тоже не сложно:
Код: "OBERON"
PROCEDURE Factor(VAR x: OPT.Node);
VAR fpar, id: OPT.Object; apar: OPT.Node;
BEGIN
IF (sym < lparen) & (sym # minus) & (sym # plus) THEN err(13); (* чем может начинаться Factor*)
REPEAT OPS.Get(sym) UNTIL sym >= lparen
END;
...
ELSIF sym = not THEN
OPS.Get(sym); Factor(x); OPB.MOp(not, x)
ELSIF sym = minus THEN (* унарный минус *)
OPS.Get(sym); Factor(x); OPB.MOp(minus, x)
ELSIF sym = plus THEN (* унарный плюс *)
OPS.Get(sym); Factor(x); OPB.MOp(plus, x)
ELSE err(13); OPS.Get(sym); x := NIL
END ;
...
PROCEDURE SimpleExpression(VAR x: OPT.Node);
VAR y: OPT.Node; addop: BYTE;
BEGIN
(* убираем + и - из SimpleExpression *)
(* IF sym = minus THEN OPS.Get(sym); Term(x); OPB.MOp(minus, x)
ELSIF sym = plus THEN OPS.Get(sym); Term(x); OPB.MOp(plus, x)
ELSE Term(x)
END ; *)
Term(x);
WHILE (plus <= sym) & (sym <= or) DO
addop := sym; OPS.Get(sym);
Term(y); OPB.Op(addop, x, y)
END
END SimpleExpression;
IF x = NIL THEN x := OPB.NewIntConst(1); x^.typ := OPT.undftyp END
END Factor;
Как видим, никакого усложнения ни в грамматике, ни в трансляторе.
Но вот стоит ли менять стандарты Оберона? Одно дело исправить FOR, потому что вряд ли кто-то использовал полезный эффект от зацикливания на краю диапазона. Другое дело - ломать устоявшийся и прошедший через все версии стандарт. По крайней мере до того, как будет понятно, почему именно такой стандарт принят.