uscripttype.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit UScriptType;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. Classes, SysUtils, BGRABitmapTypes;
  7. const
  8. VariableDefinitionToken : string = ':';
  9. TrueToken : string = 'True';
  10. FalseToken : string = 'False';
  11. UndefinedToken : string = 'None';
  12. CharToken1 : string = 'Chr';
  13. CharToken2 : string = 'Char';
  14. StringDelimiter1 = '"';
  15. StringDelimiter2 = '''';
  16. EscapePrefix = '\';
  17. StringDelimiters = [StringDelimiter1, StringDelimiter2];
  18. IdentifierCharStart: set of char = ['a'..'z','A'..'Z','_',#128..#255];
  19. IdentifierCharMiddle: set of char = ['a'..'z','A'..'Z','_',#128..#255,'0'..'9'];
  20. IgnoredWhitespaces : set of char = [#9,#13,#10,' '];
  21. ListMaxLength = 65536;
  22. type
  23. TScriptInteger = int64;
  24. PScriptInteger = ^TScriptInteger;
  25. TInterpretationError = (ieTooManyClosingBrackets, ieEndingQuoteNotFound, ieOpeningBracketNotFound, ieClosingBracketNotFound,
  26. ieConstantExpressionExpected, ieUnexpectedChar, ieInvalidNumber, ieInvalidColor, ieInvalidBoolean,
  27. ieDuplicateIdentifier, ieUnexpectedOpeningBracketKind, ieUnexpectedClosingBracketKind,
  28. ieUnknownListType, ieMissingValue, ieTooManyValues);
  29. TInterpretationErrors = set of TInterpretationError;
  30. TScriptVariableType = (svtUndefined, svtFloat, svtInteger, svtPoint, svtBoolean, svtString, svtPixel, svtSubset,
  31. svtFloatList, svtIntList, svtPointList, svtBoolList, svtStrList, svtPixList);
  32. TScriptFunctionExceptionHandler = procedure(AFunctionName: string; AException: Exception) of object;
  33. TParsedLitteral = record
  34. valueType: TScriptVariableType;
  35. valueFloat: double;
  36. valueInt: TScriptInteger;
  37. valuePoint: TPoint3D;
  38. valueBool: boolean;
  39. valueStr: string;
  40. valuePixel: TBGRAPixel;
  41. end;
  42. TScalarVariable = record
  43. name: string;
  44. varType: TScriptVariableType;
  45. case TScriptVariableType of
  46. svtFloat: (valueFloat: double);
  47. svtInteger: (valueInt: TScriptInteger);
  48. svtPoint: (valuePoint: TPoint3D);
  49. svtBoolean: (valueBool: boolean);
  50. svtPixel: (valuePix: TBGRAPixel);
  51. svtUndefined: (valueBytes: packed array[0..11] of byte);
  52. end;
  53. const
  54. ScriptVariableListTypes : set of TScriptVariableType = [svtFloatList, svtIntList, svtPointList, svtBoolList, svtStrList, svtPixList];
  55. ScriptScalarListTypes : set of TScriptVariableType = [svtFloatList, svtIntList, svtPointList, svtPixList];
  56. ScriptScalarTypes : set of TScriptVariableType = [svtFloat, svtInteger, svtPoint, svtBoolean, svtPixel];
  57. ScalarListElementSize : array[svtFloatList..svtPixList] of NativeInt =
  58. (sizeof(double), sizeof(TScriptInteger), sizeof(TPoint3D), 0, 0, sizeof(TBGRAPixel));
  59. ListElementType : array[svtFloatList..svtPixList] of TScriptVariableType =
  60. (svtFloat, svtInteger, svtPoint, svtBoolean, svtString, svtPixel);
  61. EmptyListExpression : array[svtFloatList..svtPixList] of string =
  62. ('[~0.0]', '[~0]', '[(0.0,0.0)]', '[~False]', '[~""]','[~#000]');
  63. InterpretationErrorToStr: array[TInterpretationError] of string =
  64. ('Too many closing brackets', 'Ending quote not found',
  65. 'Opening bracket not found', 'Closing bracket not found',
  66. 'Constant expression expected', 'Unexpected char',
  67. 'Invalid number', 'Invalid color', 'Invalid boolean',
  68. 'Duplicate identifier', 'Unexpected opening bracket kind',
  69. 'Unexpected closing bracket kind',
  70. 'Unknown list type', 'Missing value', 'Too many values');
  71. function ScriptQuote(const S: string): string;
  72. function ScriptUnquote(const S: string): string;
  73. function UnescapeString(const S: string): string;
  74. function TryScriptUnquote(const S: String; out unquotedS: string): TInterpretationErrors;
  75. function FloatToStrUS(AValue: double; AExplicitDot: boolean = true): string;
  76. function ScalarToStr(AVarType: TScriptVariableType; const AValue): string;
  77. function ParseLitteral(var cur: integer; expr: string; var errors: TInterpretationErrors): TParsedLitteral;
  78. function ParseListType(s: string): TScriptVariableType;
  79. function FloatToPixel(AValue: double): TBGRAPixel;
  80. function IntToPixel(AValue: TScriptInteger): TBGRAPixel;
  81. function PixelToInt(AValue: TBGRAPixel): TScriptInteger;
  82. function InterpretationErrorsToStr(AErrors: TInterpretationErrors): string;
  83. function ScriptGuidToStr(const AGuid: TGuid): string;
  84. function ScriptStrToGuid(AValue: string): TGuid;
  85. implementation
  86. uses BGRAUTF8;
  87. {$i quote.inc}
  88. function FloatToStrUS(AValue: double; AExplicitDot: boolean = true): string;
  89. var idxE,idxPt,beforeE,afterE: integer;
  90. begin
  91. if frac(AValue) = 0 then
  92. str(AValue:15:0, result)
  93. else
  94. str(AValue,result);
  95. result := trim(result);
  96. idxE := pos('E',result);
  97. idxPt := pos('.',result);
  98. if (idxE <> 0) and (idxPt < idxE) then
  99. begin
  100. beforeE := idxE;
  101. while (beforeE > 1) and (result[beforeE-1]='0') do dec(beforeE);
  102. if (beforeE > 1) and (result[beforeE-1]='.') then dec(beforeE);
  103. delete(result,beforeE,idxE-beforeE);
  104. idxE := pos('E',result);
  105. if (idxE < length(result)) and (result[idxE+1]='-') then inc(idxE);
  106. afterE := idxE;
  107. if (afterE < length(result)) and (result[afterE+1]='+') then inc(afterE);
  108. while (afterE < length(result)) and (result[afterE+1]='0') do inc(afterE);
  109. if (afterE = length(result)) then
  110. begin
  111. if (idxE > 1) and (result[idxE] = 'E') then dec(idxE);
  112. end;
  113. delete(result,idxE+1,afterE-idxE);
  114. idxE := pos('E',result);
  115. idxPt := pos('.',result);
  116. if copy(result,idxE,length(result)-idxE+1)='E-1' then
  117. begin
  118. if idxPt >= 1 then
  119. begin
  120. delete(result,idxPt,1);
  121. if idxPt > 1 then dec(idxPt) else result := '0'+result;
  122. insert('.',result,idxPt);
  123. if (idxPt = 1) or (result[idxPt-1] = '-') then insert('0',result,idxPt);
  124. end else
  125. result := '0.' + result;
  126. idxE := pos('E',result);
  127. delete(result,idxE,length(result)-idxE+1);
  128. end;
  129. end;
  130. idxE := pos('E',result);
  131. idxPt := pos('.',result);
  132. if AExplicitDot and (idxE = 0) and (idxPt = 0) then result := result+'.0';
  133. end;
  134. function ScalarToStr(AVarType: TScriptVariableType; const AValue): string;
  135. begin
  136. case AVarType of
  137. svtFloat: result := FloatToStrUS(double(AValue));
  138. svtInteger: result := IntToStr(TScriptInteger(AValue));
  139. svtPoint: with TPoint3D(AValue) do
  140. begin
  141. if z <> EmptySingle then
  142. result := '(' + FloatToStrUS(x, false)+', '+FloatToStrUS(y, false)+', '+FloatToStrUS(z, false)+')'
  143. else
  144. result := '(' + FloatToStrUS(x, false)+', '+FloatToStrUS(y, false)+')';
  145. end;
  146. svtPixel: result := '#'+BGRAToStr(TBGRAPixel(AValue), nil,0,true);
  147. svtBoolean: result := BoolToStr(Boolean(AValue),TrueToken,FalseToken);
  148. else raise exception.Create('Not a scalar type');
  149. end;
  150. end;
  151. function ParseLitteral(var cur: integer; expr: string; var errors: TInterpretationErrors): TParsedLitteral;
  152. var startIdentifier: integer;
  153. inIdentifier, notConstant: boolean;
  154. inBracket: integer;
  155. isString, isBoolean, isUndefined: boolean;
  156. procedure CheckIdentifier;
  157. var idStr: string;
  158. begin
  159. inIdentifier:= false;
  160. idStr := copy(expr,startIdentifier,cur-startIdentifier);
  161. if (CompareText(idStr,CharToken1) = 0) or (CompareText(idStr,CharToken2) = 0) then
  162. begin
  163. if inBracket = 0 then isString := true;
  164. end else
  165. if (CompareText(idStr,TrueToken) = 0) or (CompareText(idStr,FalseToken) = 0) then
  166. begin
  167. if inBracket = 0 then isBoolean := true;
  168. end
  169. else
  170. if (CompareText(idStr,UndefinedToken) = 0) then
  171. begin
  172. if inBracket = 0 then isUndefined := true;
  173. end
  174. else
  175. notConstant := true;
  176. end;
  177. var
  178. previousChar: char;
  179. valueStr: string;
  180. start: integer;
  181. unquotedStr: string;
  182. inQuote: char;
  183. inNumber, inPixel: boolean;
  184. isNumber, isPixel: boolean;
  185. valueInt: TScriptInteger;
  186. valueFloat: double;
  187. valueBool: boolean;
  188. valuePixel: TBGRAPixel;
  189. errPos,coordIndex,posComma: integer;
  190. missingFlag,errorFlag: boolean;
  191. begin
  192. result.valueType := svtUndefined;
  193. result.valueFloat := 0;
  194. result.valueInt := 0;
  195. result.valuePixel := BGRAPixelTransparent;
  196. result.valueBool:= false;
  197. start := cur;
  198. inBracket:= 0;
  199. inQuote:= #0;
  200. inIdentifier:= false;
  201. inNumber:= false;
  202. inPixel:= false;
  203. previousChar := #0;
  204. isString := false;
  205. isBoolean:= false;
  206. isNumber:= false;
  207. isPixel := false;
  208. isUndefined := false;
  209. startIdentifier:= 1; //initialize
  210. notConstant:= false;
  211. while cur <= length(expr) do
  212. begin
  213. if inQuote<>#0 then
  214. begin
  215. if expr[cur] = inQuote then inQuote := #0 else
  216. if expr[cur] in[#13,#10] then
  217. begin
  218. errors += [ieEndingQuoteNotFound];
  219. break;
  220. end;
  221. end else
  222. begin
  223. if inIdentifier then
  224. begin
  225. if not (expr[cur] in IdentifierCharMiddle) then
  226. CheckIdentifier;
  227. end else
  228. if inNumber then
  229. begin
  230. if not ((expr[cur] in['0'..'9','.','e','E']) or
  231. ((expr[cur] in['-','+']) and (previousChar in ['e','E']))) then
  232. inNumber:= false;
  233. end else
  234. if inPixel then
  235. begin
  236. if not (expr[cur] in['0'..'9','a'..'f','A'..'F']) then
  237. inPixel:= false;
  238. end;
  239. if not inNumber and not inIdentifier and not inPixel then
  240. begin
  241. if expr[cur] in['(','['] then inc(inBracket) else
  242. if expr[cur] in[')',']'] then
  243. begin
  244. dec(inBracket);
  245. if inBracket < 0 then errors += [ieTooManyClosingBrackets];
  246. end else
  247. if expr[cur] in StringDelimiters then
  248. begin
  249. inQuote := expr[cur];
  250. if inBracket = 0 then isString:= true;
  251. end else
  252. if expr[cur] in IdentifierCharStart then
  253. begin
  254. inIdentifier := true;
  255. startIdentifier:= cur;
  256. end
  257. else
  258. if expr[cur] in['0'..'9','.'] then
  259. begin
  260. inNumber := true;
  261. if inBracket = 0 then isNumber:= true;
  262. end
  263. else
  264. if expr[cur] = '#' then
  265. begin
  266. inPixel := true;
  267. if inBracket = 0 then IsPixel:= true;
  268. end
  269. else
  270. if (expr[cur] in[',','}']) and (inBracket = 0) then break;
  271. end;
  272. end;
  273. previousChar:= expr[cur];
  274. inc(cur);
  275. end;
  276. if inNumber then inNumber:= false;
  277. if inPixel then inPixel := false;
  278. if inIdentifier then CheckIdentifier;
  279. if inQuote<>#0 then errors += [ieEndingQuoteNotFound];
  280. if inBracket > 0 then errors += [ieClosingBracketNotFound];
  281. if notConstant then errors += [ieConstantExpressionExpected];
  282. valueStr := Trim(copy(expr,start,cur-start));
  283. if isUndefined then
  284. begin
  285. result.valueType := svtUndefined;
  286. end else
  287. if isString then
  288. begin
  289. errors := errors + TryScriptUnquote(valueStr, unquotedStr);
  290. result.valueType := svtString;
  291. result.valueStr := unquotedStr;
  292. end else
  293. if isBoolean then
  294. begin
  295. if not TryStrToBool(valueStr, valueBool) then
  296. errors := errors + [ieInvalidBoolean] else
  297. begin
  298. result.valueType := svtBoolean;
  299. result.valueBool := valueBool;
  300. end;
  301. end else
  302. if isNumber then
  303. begin
  304. if pos('.',valueStr) = 0 then
  305. begin
  306. val(valueStr,valueInt,errPos);
  307. if errPos <> 0 then errors := errors + [ieInvalidNumber]
  308. else
  309. begin
  310. result.valueType := svtInteger;
  311. result.valueInt := valueInt;
  312. end;
  313. end else
  314. begin
  315. val(valueStr,valueFloat,errPos);
  316. if errPos <> 0 then errors := errors + [ieInvalidNumber]
  317. else
  318. begin
  319. result.valueType := svtFloat;
  320. result.valueFloat := valueFloat;
  321. end;
  322. end;
  323. end else
  324. if isPixel then
  325. begin
  326. valuePixel := BGRABlack;
  327. TryStrToBGRA(valueStr,valuePixel,missingFlag,errorFlag);
  328. if errorFlag or missingFlag then errors := errors + [ieInvalidColor]
  329. else
  330. begin
  331. result.valueType:= svtPixel;
  332. result.valuePixel := valuePixel;
  333. end;
  334. end else
  335. if (length(valueStr)>=2) and (valueStr[1] = '(') and (valueStr[length(valueStr)] = ')') then
  336. begin
  337. result.valuePoint:= Point3D(0,0,EmptySingle);
  338. valueStr := trim(copy(valueStr,2,length(valueStr)-2));
  339. coordIndex := 0;
  340. while valueStr<>'' do
  341. begin
  342. if coordIndex >= 3 then
  343. begin
  344. errors := errors + [ieTooManyValues];
  345. break;
  346. end;
  347. posComma := pos(',', valueStr);
  348. if posComma > 0 then
  349. val(copy(valueStr,1,posComma-1),valueFloat,errPos)
  350. else
  351. val(valueStr,valueFloat,errPos);
  352. if errPos <> 0 then
  353. begin
  354. errors := errors + [ieInvalidNumber];
  355. break;
  356. end;
  357. case coordIndex of
  358. 0: result.valuePoint.x := valueFloat;
  359. 1: result.valuePoint.y := valueFloat;
  360. 2: result.valuePoint.z := valueFloat;
  361. end;
  362. inc(coordIndex);
  363. if posComma = 0 then valueStr := ''
  364. else delete(valueStr, 1, posComma);
  365. end;
  366. if coordIndex >= 2 then
  367. result.valueType:= svtPoint;
  368. end else
  369. errors := errors + [ieConstantExpressionExpected];
  370. end;
  371. function ParseListType(s: string): TScriptVariableType;
  372. var cur,start,inPar: integer;
  373. inQuote: boolean;
  374. firstVal: TParsedLitteral;
  375. errors: TInterpretationErrors;
  376. begin
  377. s := trim(s);
  378. if (length(s)>0) and (s[1]='[') then cur := 2 else cur := 1;
  379. while (cur <= length(s)) and (s[cur] in IgnoredWhitespaces) do inc(cur);
  380. if (cur <= length(s)) and (s[cur]='~') then inc(cur);
  381. while (cur <= length(s)) and (s[cur] in IgnoredWhitespaces) do inc(cur);
  382. inQuote:= false;
  383. inPar := 0;
  384. start := cur;
  385. while (cur <= length(s)) do
  386. begin
  387. if inQuote then
  388. begin
  389. if s[cur]='"' then inQuote:= false;
  390. end else
  391. begin
  392. if s[cur]='"' then inQuote:= true else
  393. if s[cur]='(' then inc(inPar) else
  394. if s[cur]=')' then
  395. begin
  396. if inPar > 0 then dec(inPar) else break;
  397. end else
  398. if (inPar = 0) and (s[cur] in ['[',']',',']) then break;
  399. end;
  400. inc(cur);
  401. end;
  402. s := copy(s,start,cur-start);
  403. cur := 1;
  404. errors := [];
  405. firstVal := ParseLitteral(cur,s,errors);
  406. case firstval.valueType of
  407. svtBoolean: result := svtBoolList;
  408. svtFloat: result := svtFloatList;
  409. svtPoint: result := svtPointList;
  410. svtInteger: result := svtIntList;
  411. svtPixel: result := svtPixList;
  412. svtString: result := svtStrList;
  413. svtUndefined:
  414. begin
  415. include(errors, ieUnknownListType);
  416. result := svtUndefined;
  417. end
  418. else
  419. result := svtUndefined;
  420. end;
  421. end;
  422. function FloatToPixel(AValue: double): TBGRAPixel;
  423. var byteValue: byte;
  424. begin
  425. if AValue <= 0 then result := BGRABlack else
  426. if AValue >= 255 then result := BGRAWhite else
  427. begin
  428. byteValue := round(AValue);
  429. result := BGRA(byteValue,byteValue,byteValue,255);
  430. end;
  431. end;
  432. function IntToPixel(AValue: TScriptInteger): TBGRAPixel;
  433. begin
  434. if AValue <= 0 then result := BGRABlack else
  435. if AValue >= 255 then result := BGRAWhite else
  436. result := BGRA(AValue,AValue,AValue,255);
  437. end;
  438. function PixelToInt(AValue: TBGRAPixel): TScriptInteger;
  439. begin
  440. result := AValue.ToGrayscale.green;
  441. end;
  442. function InterpretationErrorsToStr(AErrors: TInterpretationErrors): string;
  443. var
  444. e: TInterpretationError;
  445. begin
  446. result := '';
  447. for e := low(TInterpretationError) to high(TInterpretationError) do
  448. if e in AErrors then
  449. begin
  450. if result <> '' then result += ', ';
  451. result += InterpretationErrorToStr[e];
  452. end;
  453. end;
  454. function ScriptGuidToStr(const AGuid: TGuid): string;
  455. begin
  456. result := LowerCase(GUIDToString(AGuid));
  457. if (length(result)>0) and (result[1]='{') and (result[length(result)]='}') then
  458. result := copy(result,2,length(result)-2);
  459. end;
  460. function ScriptStrToGuid(AValue: string): TGuid;
  461. begin
  462. if not TryStringToGUID('{'+AValue+'}', result) then
  463. result := GUID_NULL;
  464. end;
  465. end.