BrookMathExpression.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. (* _ _
  2. * | |__ _ __ ___ ___ | | __
  3. * | '_ \| '__/ _ \ / _ \| |/ /
  4. * | |_) | | | (_) | (_) | <
  5. * |_.__/|_| \___/ \___/|_|\_\
  6. *
  7. * Microframework which helps to develop web Pascal applications.
  8. *
  9. * Copyright (c) 2012-2020 Silvio Clecio <[email protected]>
  10. *
  11. * Brook framework is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU Lesser General Public
  13. * License as published by the Free Software Foundation; either
  14. * version 2.1 of the License, or (at your option) any later version.
  15. *
  16. * Brook framework is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  19. * Lesser General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Lesser General Public
  22. * License along with Brook framework; if not, write to the Free Software
  23. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  24. *)
  25. { Contains class to evaluate mathematical expressions. }
  26. unit BrookMathExpression;
  27. {$I BrookDefines.inc}
  28. interface
  29. uses
  30. SysUtils,
  31. Math,
  32. {$IFNDEF FPC}
  33. AnsiStrings,
  34. {$ENDIF}
  35. Classes,
  36. Marshalling,
  37. libsagui,
  38. BrookHandledClasses;
  39. resourcestring
  40. { Error message @code('Inactive math expression.'). }
  41. SBrookInactiveMathExpression = 'Inactive math expression.';
  42. type
  43. { Possible error types returned by the mathematical expression evaluator. }
  44. TBrookMathExpressionErrorKind = (
  45. { None error. }
  46. ekNone,
  47. { Error not related to evaluation. }
  48. ekUnknown,
  49. { Unexpected number, e.g. '(1+2)3'. }
  50. ekUnexpectedNumber,
  51. { Unexpected word, e.g. '(1+2)x'. }
  52. ekUnexpectedWord,
  53. { Unexpected parenthesis, e.g. '1(2+3)'. }
  54. ekUnexpectedParens,
  55. { Missing expected operand, e.g. '0^+1'. }
  56. ekMissingOperand,
  57. { Unknown operator, e.g. '(1+2).'. }
  58. ekUnknownOperator,
  59. { Invalid function name, e.g. 'unknownfunc()'. }
  60. ekInvalidFuncName,
  61. { Bad parenthesis, e.g. '(1+2'. }
  62. ekBadParens,
  63. { Too few arguments passed to a macro, e.g. '$()'. }
  64. ekTooFewFuncArgs,
  65. { First macro argument is not variable, e.g. '$(1)'. }
  66. ekFirstArgIsNotVar,
  67. { Bad variable name, e.g. '2.3.4'. }
  68. ekBadVariableName,
  69. { Bad assignment, e.g. '2=3'. }
  70. ekBadAssignment
  71. );
  72. { Structured type which holds the mathematical expression errors. }
  73. TBrookMathExpressionError = packed record
  74. private
  75. FHandle: Psg_expr;
  76. function GetNear: Integer;
  77. function GetKind: TBrookMathExpressionErrorKind;
  78. function GetMessage: string;
  79. function GetHandle: Pointer;
  80. public
  81. { Creates an instance of @code(TBrookMathExpressionError).
  82. @param(AHandle[in] Math expression error handle.) }
  83. constructor Create(AHandle: Pointer);
  84. { Nearby position of an error in the mathematical expression. }
  85. property Near: Integer read GetNear;
  86. { Kind of an error in the mathematical expression. }
  87. property Kind: TBrookMathExpressionErrorKind read GetKind;
  88. { Description of an error in the mathematical expression. }
  89. property Message: string read GetMessage;
  90. { Math expression error handle. }
  91. property Handle: Pointer read GetHandle;
  92. end;
  93. { Event signature used to handle errors in a mathematical expression. }
  94. TBrookMathExpressionErrorEvent = procedure(ASender: TObject;
  95. AError: TBrookMathExpressionError) of object;
  96. { Structured type which holds the properties of a math expression extension. }
  97. TBrookMathExpressionExtension = packed record
  98. private
  99. FHandle: Psg_expr_argument;
  100. FIdent: MarshaledAString;
  101. function GetHasArgs: Boolean;
  102. function GetArg(AIndex: Integer): Double;
  103. function GetIdent: string;
  104. function GetHandle: Pointer;
  105. public
  106. { Creates an instance of @code(TBrookMathExpressionExtension).
  107. @param(AHandle[in] Math expression extension handle.)
  108. @param(AIdent[in] Function identifier.) }
  109. constructor Create(AHandle: Pointer; const AIdent: MarshaledAString);
  110. { Indicates that extension contains arguments. }
  111. property HasArgs: Boolean read GetHasArgs;
  112. { Function argument by its index. }
  113. property Args[AIndex: Integer]: Double read GetArg; default;
  114. { Function identifier. }
  115. property Ident: string read GetIdent;
  116. { Extension handle. }
  117. property Handle: Pointer read GetHandle;
  118. end;
  119. { Event signature used to handle extension in a mathematical expression. }
  120. TBrookMathExpressionExtensionEvent = function(ASender: TObject;
  121. AExtension: TBrookMathExpressionExtension): Double of object;
  122. { Mathematical expression evaluator. }
  123. TBrookMathExpression = class(TBrookHandledComponent)
  124. private
  125. FExtensions: TStringList;
  126. FExtensionsHandle: array of sg_expr_extension;
  127. FOnExtension: TBrookMathExpressionExtensionEvent;
  128. FOnError: TBrookMathExpressionErrorEvent;
  129. FOnActivate: TNotifyEvent;
  130. FOnDeactivate: TNotifyEvent;
  131. FHandle: Psg_expr;
  132. FExpression: string;
  133. FActive: Boolean;
  134. FStreamedActive: Boolean;
  135. FCompiled: Boolean;
  136. procedure DoExtensionsChange(Sender: TObject);
  137. function IsActiveStored: Boolean;
  138. procedure SetActive(AValue: Boolean);
  139. procedure SetExtensions(AValue: TStringList);
  140. procedure SetExpression(const AValue: string);
  141. procedure InternalLibUnloadEvent(ASender: TObject);
  142. protected
  143. function CreateExtensions: TStringList; virtual;
  144. class function DoExprFunc(Acls: Pcvoid; Aargs: Psg_expr_argument;
  145. const Aidentifier: Pcchar): cdouble; cdecl; static;
  146. procedure Loaded; override;
  147. function GetHandle: Pointer; override;
  148. function DoExtension(ASender: TObject;
  149. AExtension: TBrookMathExpressionExtension): Double; virtual;
  150. procedure DoError(ASender: TObject;
  151. AError: TBrookMathExpressionError); virtual;
  152. procedure DoOpen; virtual;
  153. procedure DoClose; virtual;
  154. procedure CheckActive; inline;
  155. public
  156. { Creates an instance of @code(TBrookMathExpression).
  157. @param(AOwner[in] Owner component.) }
  158. constructor Create(AOwner: TComponent); override;
  159. { Destroys an instance of @code(TBrookMathExpression). }
  160. destructor Destroy; override;
  161. { Opens the math expression. }
  162. procedure Open;
  163. { Closes the math expression. }
  164. procedure Close;
  165. { Compiles a mathematical expression returning the errors if it does not
  166. succeed.
  167. @param(AExpression[in] Mathematical expression.)
  168. @param(AError[out] Mathematical expression error.)
  169. @returns(@True if compilation succeeds.) }
  170. function Compile(const AExpression: string;
  171. out AError: TBrookMathExpressionError): Boolean; overload; virtual;
  172. { Compiles a mathematical expression indicating if it succeeds.
  173. @param(AExpression[in] Mathematical expression.)
  174. @returns(@True if compilation succeeds.) }
  175. function Compile(const AExpression: string): Boolean; overload; virtual;
  176. { Clears a mathematical expression instance. }
  177. procedure Clear; virtual;
  178. { Evaluates a compiled mathematical expression.
  179. @returns(Evaluated mathematical expression) }
  180. function Evaluate: Double; virtual;
  181. { Gets the value of a declared variable.
  182. @param(AName[in] Name of the declared variable.)
  183. @returns(Value of a declared variable.) }
  184. function GetVariable(const AName: string): Double; virtual;
  185. { Sets a variable to the mathematical expression.
  186. @param(AName[in] Name for the variable.)
  187. @param(AValue[in] Value for the variable.) }
  188. procedure SetVariable(const AName: string; const AValue: Double); virtual;
  189. { Indicates if a mathematical expression has been successfully compiled. }
  190. property Compiled: Boolean read FCompiled;
  191. { Gets or sets a mathematical expression variable. }
  192. property Variables[const AName: string]: Double read GetVariable
  193. write SetVariable; default;
  194. published
  195. { Activates the mathematical expression evaluator. }
  196. property Active: Boolean read FActive write SetActive stored IsActiveStored;
  197. { Declares the mathematical expression. }
  198. property Expression: string read FExpression write SetExpression;
  199. { Declares the list of mathematical expression extensions. }
  200. property Extensions: TStringList read FExtensions write SetExtensions;
  201. { Event triggered when an extensions is called in the mathematical
  202. expression. }
  203. property OnExtension: TBrookMathExpressionExtensionEvent read FOnExtension
  204. write FOnExtension;
  205. { Event triggered when a mathematical expression compilation fails. }
  206. property OnError: TBrookMathExpressionErrorEvent read FOnError
  207. write FOnError;
  208. { Event triggered when the math expression component is enabled. }
  209. property OnActivate: TNotifyEvent read FOnActivate write FOnActivate;
  210. { Event triggered when the math expression component is disabled. }
  211. property OnDeactivate: TNotifyEvent read FOnDeactivate write FOnDeactivate;
  212. end;
  213. { Evaluates a mathematical expression. }
  214. function Evaluate(const AExpression: string): Double;
  215. implementation
  216. function Evaluate(const AExpression: string): Double;
  217. var
  218. M: TMarshaller;
  219. begin
  220. SgLib.Check;
  221. Result := sg_expr_calc(M.ToCString(AExpression), Length(AExpression));
  222. end;
  223. { TBrookMathExpressionError }
  224. constructor TBrookMathExpressionError.Create(AHandle: Pointer);
  225. begin
  226. FHandle := AHandle;
  227. end;
  228. function TBrookMathExpressionError.GetHandle: Pointer;
  229. begin
  230. Result := FHandle;
  231. end;
  232. function TBrookMathExpressionError.GetNear: Integer;
  233. begin
  234. SgLib.Check;
  235. Result := sg_expr_near(FHandle);
  236. end;
  237. function TBrookMathExpressionError.GetKind: TBrookMathExpressionErrorKind;
  238. begin
  239. SgLib.Check;
  240. Result := TBrookMathExpressionErrorKind(Succ(sg_expr_err(FHandle)));
  241. end;
  242. function TBrookMathExpressionError.GetMessage: string;
  243. begin
  244. SgLib.Check;
  245. Result := TMarshal.ToString(sg_expr_strerror(FHandle));
  246. end;
  247. { TBrookMathExpressionExtension }
  248. constructor TBrookMathExpressionExtension.Create(AHandle: Pointer;
  249. const AIdent: MarshaledAString);
  250. begin
  251. FHandle := AHandle;
  252. FIdent := AIdent;
  253. end;
  254. function TBrookMathExpressionExtension.GetHandle: Pointer;
  255. begin
  256. Result := FHandle;
  257. end;
  258. function TBrookMathExpressionExtension.GetHasArgs: Boolean;
  259. begin
  260. Result := Assigned(FHandle);
  261. end;
  262. function TBrookMathExpressionExtension.GetArg(AIndex: Integer): Double;
  263. begin
  264. if not Assigned(FHandle) then
  265. Exit(NaN);
  266. SgLib.Check;
  267. Result := sg_expr_arg(FHandle, AIndex);
  268. end;
  269. function TBrookMathExpressionExtension.GetIdent: string;
  270. begin
  271. Result := TMarshal.ToString(FIdent);
  272. end;
  273. { TBrookMathExpression }
  274. constructor TBrookMathExpression.Create(AOwner: TComponent);
  275. begin
  276. inherited Create(AOwner);
  277. FExtensions := CreateExtensions;
  278. FExtensions.OnChange := DoExtensionsChange;
  279. SgLib.UnloadEvents.Add(InternalLibUnloadEvent, Self);
  280. end;
  281. destructor TBrookMathExpression.Destroy;
  282. begin
  283. SetActive(False);
  284. FExtensions.Free;
  285. SgLib.UnloadEvents.Remove(InternalLibUnloadEvent);
  286. inherited Destroy;
  287. end;
  288. class function TBrookMathExpression.DoExprFunc(Acls: Pcvoid;
  289. Aargs: Psg_expr_argument; const Aidentifier: Pcchar): cdouble;
  290. begin
  291. Result := TBrookMathExpression(Acls).DoExtension(Acls,
  292. TBrookMathExpressionExtension.Create(Aargs, Aidentifier));
  293. end;
  294. function TBrookMathExpression.CreateExtensions: TStringList;
  295. begin
  296. Result := TStringList.Create;
  297. end;
  298. procedure TBrookMathExpression.CheckActive;
  299. begin
  300. if (not (csLoading in ComponentState)) and (not Active) then
  301. raise EInvalidOpException.Create(SBrookInactiveMathExpression);
  302. end;
  303. procedure TBrookMathExpression.Loaded;
  304. begin
  305. inherited Loaded;
  306. try
  307. if FStreamedActive then
  308. SetActive(True);
  309. except
  310. if csDesigning in ComponentState then
  311. begin
  312. if Assigned(ApplicationHandleException) then
  313. ApplicationHandleException(ExceptObject)
  314. else
  315. ShowException(ExceptObject, ExceptAddr);
  316. end
  317. else
  318. raise;
  319. end;
  320. end;
  321. function TBrookMathExpression.GetHandle: Pointer;
  322. begin
  323. Result := FHandle;
  324. end;
  325. procedure TBrookMathExpression.InternalLibUnloadEvent(ASender: TObject);
  326. begin
  327. if Assigned(ASender) then
  328. TBrookMathExpression(ASender).Close;
  329. end;
  330. procedure TBrookMathExpression.DoExtensionsChange(Sender: TObject);
  331. begin
  332. Clear;
  333. end;
  334. function TBrookMathExpression.DoExtension(ASender: TObject;
  335. AExtension: TBrookMathExpressionExtension): Double;
  336. begin
  337. if Assigned(FOnExtension) then
  338. Exit(FOnExtension(ASender, AExtension));
  339. Result := NaN;
  340. end;
  341. procedure TBrookMathExpression.DoError(ASender: TObject;
  342. AError: TBrookMathExpressionError);
  343. begin
  344. if Assigned(FOnError) then
  345. FOnError(ASender, AError);
  346. end;
  347. procedure TBrookMathExpression.SetExpression(const AValue: string);
  348. begin
  349. if AValue = FExpression then
  350. Exit;
  351. FExpression := AValue;
  352. Clear;
  353. end;
  354. procedure TBrookMathExpression.SetExtensions(AValue: TStringList);
  355. begin
  356. if Assigned(AValue) then
  357. FExtensions.Assign(AValue)
  358. else
  359. FExtensions.Clear;
  360. end;
  361. procedure TBrookMathExpression.DoOpen;
  362. begin
  363. if Assigned(FHandle) then
  364. Exit;
  365. SgLib.Check;
  366. FHandle := sg_expr_new;
  367. FActive := Assigned(FHandle);
  368. if Assigned(FOnActivate) then
  369. FOnActivate(Self);
  370. end;
  371. procedure TBrookMathExpression.DoClose;
  372. begin
  373. Clear;
  374. if not Assigned(FHandle) then
  375. Exit;
  376. SgLib.Check;
  377. sg_expr_free(FHandle);
  378. FHandle := nil;
  379. FActive := False;
  380. FCompiled := False;
  381. if Assigned(FOnDeactivate) then
  382. FOnDeactivate(Self);
  383. end;
  384. function TBrookMathExpression.IsActiveStored: Boolean;
  385. begin
  386. Result := FActive;
  387. end;
  388. procedure TBrookMathExpression.SetActive(AValue: Boolean);
  389. begin
  390. if AValue = FActive then
  391. Exit;
  392. if csDesigning in ComponentState then
  393. begin
  394. if not (csLoading in ComponentState) then
  395. SgLib.Check;
  396. FActive := AValue;
  397. end
  398. else
  399. if AValue then
  400. begin
  401. if csReading in ComponentState then
  402. FStreamedActive := True
  403. else
  404. DoOpen;
  405. end
  406. else
  407. DoClose;
  408. end;
  409. function TBrookMathExpression.Compile(const AExpression: string;
  410. out AError: TBrookMathExpressionError): Boolean;
  411. var
  412. EX: sg_expr_extension;
  413. M: TMarshaller;
  414. I: Integer;
  415. R: cint;
  416. S: string;
  417. begin
  418. if FCompiled then
  419. Exit(True);
  420. CheckActive;
  421. SetLength(FExtensionsHandle, Succ(FExtensions.Count));
  422. for I := 0 to Pred(FExtensions.Count) do
  423. begin
  424. S := FExtensions[I];
  425. if S.Trim.IsEmpty then
  426. Continue;
  427. EX.func := DoExprFunc;
  428. EX.identifier :=
  429. {$IFNDEF FPC}
  430. AnsiStrings.StrNew(
  431. {$ENDIF}
  432. M.ToCString(S)
  433. {$IFNDEF FPC}
  434. )
  435. {$ENDIF};
  436. EX.cls := Self;
  437. FExtensionsHandle[I] := EX;
  438. end;
  439. FExtensionsHandle[FExtensions.Count] := Default(sg_expr_extension);
  440. R := sg_expr_compile(FHandle, M.ToCString(AExpression), Length(AExpression),
  441. @FExtensionsHandle[0]);
  442. FCompiled := R = 0;
  443. if not FCompiled then
  444. AError := TBrookMathExpressionError.Create(FHandle);
  445. Result := FCompiled;
  446. end;
  447. function TBrookMathExpression.Compile(const AExpression: string): Boolean;
  448. var
  449. E: TBrookMathExpressionError;
  450. begin
  451. Result := Compile(AExpression, E);
  452. if not Result then
  453. DoError(Self, E);
  454. end;
  455. procedure TBrookMathExpression.Clear;
  456. {$IFNDEF FPC}
  457. var
  458. I: Integer;
  459. {$ENDIF}
  460. begin
  461. if not Assigned(FHandle) then
  462. Exit;
  463. SgLib.Check;
  464. SgLib.CheckLastError(sg_expr_clear(FHandle));
  465. {$IFNDEF FPC}
  466. for I := Low(FExtensionsHandle) to Pred(High(FExtensionsHandle)) do
  467. AnsiStrings.StrDispose(FExtensionsHandle[I].identifier);
  468. {$ENDIF}
  469. FExtensionsHandle := nil;
  470. FCompiled := False;
  471. end;
  472. function TBrookMathExpression.Evaluate: Double;
  473. begin
  474. if not FCompiled then
  475. Compile(FExpression);
  476. Result := sg_expr_eval(FHandle);
  477. end;
  478. function TBrookMathExpression.GetVariable(const AName: string): Double;
  479. var
  480. M: TMarshaller;
  481. begin
  482. CheckActive;
  483. Result := sg_expr_var(FHandle, M.ToCString(AName), Length(AName));
  484. end;
  485. procedure TBrookMathExpression.SetVariable(const AName: string;
  486. const AValue: Double);
  487. var
  488. M: TMarshaller;
  489. begin
  490. CheckActive;
  491. SgLib.CheckLastError(sg_expr_set_var(FHandle, M.ToCString(AName),
  492. Length(AName), AValue));
  493. end;
  494. procedure TBrookMathExpression.Open;
  495. begin
  496. SetActive(True);
  497. end;
  498. procedure TBrookMathExpression.Close;
  499. begin
  500. SetActive(False);
  501. end;
  502. end.