{$mode objfpc} {$coperators+} {$modeswitch duplicatelocals}
uses
SysUtils, StrUtils, Math;
type
Calculator = object
class function Calculate(const s: string): double; static;
private type
OpEnum = (NoOp, PlusOp, MinusOp, MulOp, DivOp, PowOp);
const
OpSyms: array[PlusOp .. High(OpEnum)] of char = '+-*/^';
OpPrecedences: array[OpEnum] of int32 = (-1, 0, 0, 1, 1, 2);
RightAssocOps = [PowOp];
var
s: string;
p: SizeInt;
function ScanPart(lhs: double; minPrecedence: SizeInt): double;
function ScanPrimary: double;
procedure SkipSpaces;
procedure Fail(const what: string);
function RecognizeOperator: OpEnum;
end;
class function Calculator.Calculate(const s: string): double;
var
c: Calculator;
begin
c.s := s;
c.p := 1;
result := c.ScanPart(c.ScanPrimary, 0);
if c.p <= length(s) then c.Fail('неожиданное продолжение');
end;
function Calculator.ScanPart(lhs: double; minPrecedence: SizeInt): double;
var
op, nextOp: OpEnum;
rhs: double;
begin
nextOp := RecognizeOperator;
repeat
op := nextOp;
if OpPrecedences[op] < minPrecedence then exit(lhs);
p += 1;
rhs := ScanPrimary;
nextOp := RecognizeOperator;
while OpPrecedences[nextOp] + ord(nextOp in RightAssocOps) > OpPrecedences[op] do
begin
rhs := ScanPart(rhs, OpPrecedences[op] + ord(OpPrecedences[nextOp] > OpPrecedences[op]));
nextOp := RecognizeOperator;
end;
case op of
NoOp: ;
PlusOp: lhs := lhs + rhs;
MinusOp: lhs := lhs - rhs;
MulOp: lhs := lhs * rhs;
DivOp: lhs := lhs / rhs;
PowOp: lhs := Power(lhs, rhs);
end;
until false;
end;
function Calculator.ScanPrimary: double;
var
nume: SizeInt;
begin
SkipSpaces;
if p > length(s) then Fail('неожиданный конец');
if s[p] = '(' then
begin
p += 1;
result := ScanPart(ScanPrimary(), 0);
if (p > length(s)) or (s[p] <> ')') then Fail('ожидается )');
p += 1;
exit;
end;
nume := p;
while (nume <= length(s)) and ((s[nume] in ['0' .. '9', '.']) or (nume = p) and (s[nume] in ['-', '+'])) do nume += 1;
if TryStrToFloat(Copy(s, p, nume - p), result) then
begin
p := nume;
exit;
end;
case s[p] of
'+': begin p += 1; exit(ScanPrimary()); end;
'-': begin p += 1; exit(-ScanPrimary()); end;
else Fail('ожидается число, унарный оператор, или скобка');
end;
end;
procedure Calculator.SkipSpaces;
begin
while (p <= length(s)) and (s[p] = ' ') do p += 1;
end;
procedure Calculator.Fail(const what: string);
begin
raise Exception.Create(StuffString(s, p, 0, '|') + ': ' + what + '.');
end;
function Calculator.RecognizeOperator: OpEnum;
begin
SkipSpaces;
if p <= length(s) then
for result := PlusOp to High(OpEnum) do
if OpSyms[result] = s[p] then exit;
result := NoOp;
end;
const
Examples: array[0 .. 2] of string =
(
'2 + 2 * 2',
'(2 + --3) * 2^2^(1 + 1)^2',
'2^^^2'
);
var
ex: string;
begin
for ex in Examples do
try
writeln(ex + ' = ' + FloatToStr(Calculator.Calculate(ex)));
except
on e: Exception do writeln(e.Message);
end;
end.