SimpleExpression.pas 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. unit SimpleExpression;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2024 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Evaluator for simple boolean expressions
  8. Grammar:
  9. -expression = term ('or' term)*
  10. -term = factor ('and' factor)*
  11. -factor = '(' expression ')' | 'not' factor | identifier ( '(' parameters ')' )
  12. -identifier = letter | '_' (letter | number | '_' | '\')*
  13. -parameters = string | number | boolean (',' string | number | boolean )*
  14. As a special optional rule it can insert an 'or' if an identifier is encountered
  15. at the place where an 'or' could be.
  16. Function calls within parameter lists are currently not supported, except for calls
  17. to the special ExpandConstant function.
  18. }
  19. interface
  20. type
  21. TSimpleExpression = class;
  22. TSimpleExpressionOnEvalIdentifier = function(Sender: TSimpleExpression;
  23. const Name: String; const Parameters: array of const): Boolean of object;
  24. TSimpleExpressionOnExpandConstant = function(Sender: TSimpleExpression;
  25. const Constant: String): String of object;
  26. TSimpleExpression = class
  27. private
  28. FExpression: String;
  29. FLazy: Boolean;
  30. FOnEvalIdentifier: TSimpleExpressionOnEvalIdentifier;
  31. FOnExpandConstant: TSimpleExpressionOnExpandConstant;
  32. FParametersAllowed: Boolean;
  33. FSingleIdentifierMode: Boolean;
  34. FSilentOrAllowed: Boolean;
  35. FTag: LongInt;
  36. FText: PChar;
  37. FTokenId: (tiEOF, tiOpenRound, tiCloseRound, tiComma, tiNot, tiAnd, tiOr, tiIdentifier, tiString, tiInteger, tiBoolean);
  38. FToken: String;
  39. function FReadParameters(var Parameters: array of const): Integer;
  40. function FEvalIdentifier(const InLazyBranch: Boolean): Boolean;
  41. function FEvalFactor(const InLazyBranch: Boolean): Boolean;
  42. function FEvalTerm(const InLazyBranch: Boolean): Boolean;
  43. function FEvalExpression(const InLazyBranch: Boolean): Boolean;
  44. procedure Next;
  45. public
  46. function Eval: Boolean;
  47. property Expression: String read FExpression write FExpression;
  48. property Lazy: Boolean read FLazy write FLazy;
  49. property OnEvalIdentifier: TSimpleExpressionOnEvalIdentifier read FOnEvalIdentifier write FOnEvalIdentifier;
  50. property OnExpandConstant: TSimpleExpressionOnExpandConstant read FOnExpandConstant write FOnExpandConstant;
  51. property ParametersAllowed: Boolean read FParametersAllowed write FParametersAllowed;
  52. property SilentOrAllowed: Boolean read FSilentOrAllowed write FSilentOrAllowed;
  53. property SingleIdentifierMode: Boolean read FSingleIdentifierMode write FSingleIdentifierMode;
  54. property Tag: LongInt read FTag write FTag;
  55. end;
  56. implementation
  57. uses
  58. SysUtils;
  59. procedure AssignStringToVarRec(var VarRec: TVarRec; const S: String);
  60. begin
  61. VarRec.VType := vtUnicodeString;
  62. UnicodeString(VarRec.VUnicodeString) := S;
  63. end;
  64. {---}
  65. procedure TSimpleExpression.Next;
  66. var
  67. P: PChar;
  68. begin
  69. { Ignore whitespace }
  70. while CharInSet(FText^ , [#1..#32]) do
  71. Inc(FText);
  72. case FText^ of
  73. #0:
  74. begin
  75. FToken := '';
  76. FTokenId := tiEOF;
  77. end;
  78. '(':
  79. begin
  80. FToken := FText^;
  81. FTokenId := tiOpenRound;
  82. Inc(FText);
  83. end;
  84. ')':
  85. begin
  86. FToken := FText^;
  87. FTokenId := tiCloseRound;
  88. Inc(FText);
  89. end;
  90. ',':
  91. begin
  92. FToken := FText^;
  93. FTokenId := tiComma;
  94. Inc(FText);
  95. end;
  96. 'A'..'Z', 'a'..'z', '_':
  97. begin
  98. P := FText;
  99. Inc(FText);
  100. while CharInSet(FText^ , ['0'..'9', 'A'..'Z', 'a'..'z', '_', '\']) do
  101. Inc(FText);
  102. SetString(FToken, P, FText - P);
  103. if CompareText(FToken, 'not') = 0 then
  104. FTokenId := tiNot
  105. else if CompareText(FToken, 'and') = 0 then
  106. FTokenId := tiAnd
  107. else if CompareText(FToken, 'or') = 0 then
  108. FTokenId := tiOr
  109. else if CompareText(FToken, 'true') = 0 then
  110. FTokenId := tiBoolean
  111. else if CompareText(FToken, 'false') = 0 then
  112. FTokenId := tiBoolean
  113. else
  114. FTokenId := tiIdentifier;
  115. end;
  116. '0'..'9':
  117. begin
  118. P := FText;
  119. Inc(FText);
  120. while CharInSet(FText^ , ['0'..'9']) do
  121. Inc(FText);
  122. SetString(FToken, P, FText - P);
  123. FTokenId := tiInteger;
  124. end;
  125. '''':
  126. begin
  127. FToken := '';
  128. while True do begin
  129. Inc(FText);
  130. case FText^ of
  131. #0: raise Exception.Create('Unexpected end of expression while reading string constant');
  132. #10, #13: raise Exception.Create('Unterminated string');
  133. else
  134. if FText^ = '''' then begin
  135. Inc(FText);
  136. if FText^ <> '''' then
  137. Break;
  138. end;
  139. FToken := FToken + FText^;
  140. end;
  141. end;
  142. FTokenId := tiString;
  143. end;
  144. else
  145. raise Exception.CreateFmt('Invalid symbol ''%s'' found', [FText^]);
  146. end;
  147. end;
  148. function TSimpleExpression.FReadParameters(var Parameters: array of const): Integer;
  149. var
  150. I: Integer;
  151. begin
  152. I := 0;
  153. while FTokenId in [tiIdentifier, tiString, tiInteger, tiBoolean] do begin
  154. if I <= High(Parameters) then begin
  155. if FTokenId = tiIdentifier then begin
  156. { Currently only calls to 'ExpandConstant' are supported in parameter lists }
  157. if CompareText(FToken, 'ExpandConstant') <> 0 then
  158. raise Exception.Create('Can only call function "ExpandConstant" within parameter lists');
  159. Next;
  160. if FTokenId <> tiOpenRound then
  161. raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
  162. Next;
  163. if FTokenId <> tiString then
  164. raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
  165. if Assigned(FOnExpandConstant) then
  166. AssignStringToVarRec(Parameters[I], FOnExpandConstant(Self, FToken))
  167. else
  168. AssignStringToVarRec(Parameters[I], FToken);
  169. Next;
  170. if FTokenId <> tiCloseRound then
  171. raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
  172. end else if FTokenId = tiString then begin
  173. AssignStringToVarRec(Parameters[I], FToken);
  174. end else if FTokenId = tiInteger then begin
  175. Parameters[I].VType := vtInteger;
  176. Parameters[I].vInteger := StrToInt(FToken);
  177. end else begin
  178. Parameters[I].VType := vtBoolean;
  179. Parameters[I].vBoolean := CompareText(FToken, 'true') = 0;
  180. end;
  181. Inc(I);
  182. end else
  183. raise Exception.Create('Maximum number of parameters exceeded');
  184. Next;
  185. if FTokenId <> tiComma then
  186. Break
  187. else
  188. Next;
  189. end;
  190. Result := I;
  191. end;
  192. function TSimpleExpression.FEvalIdentifier(const InLazyBranch: Boolean): Boolean;
  193. var
  194. Name: String;
  195. Parameters: array[0..9] of TVarRec;
  196. ParameterCount: Integer;
  197. I: Integer;
  198. begin
  199. Name := FToken;
  200. Next;
  201. FillChar(Parameters, SizeOf(Parameters), 0);
  202. try
  203. if FParametersAllowed and (FTokenId = tiOpenRound) then begin
  204. Next;
  205. ParameterCount := FReadParameters(Parameters);
  206. if FTokenId <> tiCloseRound then
  207. raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
  208. Next;
  209. end else
  210. ParameterCount := 0;
  211. if not Lazy or not InLazyBranch then begin
  212. if Assigned(FOnEvalIdentifier) then
  213. Result := FOnEvalIdentifier(Self, Name, Slice(Parameters, ParameterCount))
  214. else
  215. Result := True;
  216. end else
  217. Result := True; { Lazy and in lazy branch, just return something }
  218. finally
  219. for I := High(Parameters) downto Low(Parameters) do
  220. if Parameters[I].VType = vtUnicodeString then
  221. AssignStringToVarRec(Parameters[I], '');
  222. end
  223. end;
  224. function TSimpleExpression.FEvalFactor(const InLazyBranch: Boolean): Boolean;
  225. begin
  226. case FTokenId of
  227. tiOpenRound:
  228. begin
  229. Next;
  230. Result := FEvalExpression(InLazyBranch);
  231. if FTokenId <> tiCloseRound then
  232. raise Exception.Create('Invalid token');
  233. Next;
  234. end;
  235. tiNot:
  236. begin
  237. Next;
  238. Result := not FEvalFactor(InLazyBranch);
  239. end;
  240. tiIdentifier:
  241. begin
  242. Result := FEvalIdentifier(InLazyBranch);
  243. end;
  244. else
  245. raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
  246. end;
  247. end;
  248. function TSimpleExpression.FEvalTerm(const InLazyBranch: Boolean): Boolean;
  249. begin
  250. Result := FEvalFactor(InLazyBranch);
  251. while FTokenId = tiAnd do begin
  252. Next;
  253. if not Result then begin
  254. { End term result known, but continue parsing }
  255. FEvalFactor(True)
  256. end else
  257. Result := FEvalFactor(InLazyBranch);
  258. end;
  259. end;
  260. function TSimpleExpression.FEvalExpression(const InLazyBranch: Boolean): Boolean;
  261. begin
  262. Result := FEvalTerm(InLazyBranch);
  263. while (FTokenId = tiOr) or
  264. (FSilentOrAllowed and (FTokenId = tiIdentifier)) do begin
  265. if FTokenId = tiOr then
  266. Next;
  267. if Result then begin
  268. { End expression result known, but continue parsing }
  269. FEvalTerm(True)
  270. end else
  271. Result := FEvalTerm(InLazyBranch);
  272. end;
  273. end;
  274. {---}
  275. function TSimpleExpression.Eval: Boolean;
  276. begin
  277. FText := PChar(FExpression);
  278. Next;
  279. if not FSingleIdentifierMode then
  280. Result := FEvalExpression(False)
  281. else begin
  282. if FTokenId <> tiIdentifier then
  283. raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
  284. Result := FEvalIdentifier(False);
  285. end;
  286. if FTokenID <> tiEOF then
  287. raise Exception.CreateFmt('Invalid token ''%s'' found', [FToken]);
  288. end;
  289. end.