function_spec.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. /* Copyright 2017 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. import {
  16. PostScriptCompiler,
  17. PostScriptEvaluator,
  18. } from "../../src/core/function.js";
  19. import { PostScriptLexer, PostScriptParser } from "../../src/core/ps_parser.js";
  20. import { StringStream } from "../../src/core/stream.js";
  21. describe("function", function () {
  22. describe("PostScriptParser", function () {
  23. function parse(program) {
  24. const stream = new StringStream(program);
  25. const parser = new PostScriptParser(new PostScriptLexer(stream));
  26. return parser.parse();
  27. }
  28. it("parses empty programs", function () {
  29. const output = parse("{}");
  30. expect(output.length).toEqual(0);
  31. });
  32. it("parses positive numbers", function () {
  33. const number = 999;
  34. const program = parse("{ " + number + " }");
  35. const expectedProgram = [number];
  36. expect(program).toEqual(expectedProgram);
  37. });
  38. it("parses negative numbers", function () {
  39. const number = -999;
  40. const program = parse("{ " + number + " }");
  41. const expectedProgram = [number];
  42. expect(program).toEqual(expectedProgram);
  43. });
  44. it("parses negative floats", function () {
  45. const number = 3.3;
  46. const program = parse("{ " + number + " }");
  47. const expectedProgram = [number];
  48. expect(program).toEqual(expectedProgram);
  49. });
  50. it("parses operators", function () {
  51. const program = parse("{ sub }");
  52. const expectedProgram = ["sub"];
  53. expect(program).toEqual(expectedProgram);
  54. });
  55. it("parses if statements", function () {
  56. const program = parse("{ { 99 } if }");
  57. const expectedProgram = [3, "jz", 99];
  58. expect(program).toEqual(expectedProgram);
  59. });
  60. it("parses ifelse statements", function () {
  61. const program = parse("{ { 99 } { 44 } ifelse }");
  62. const expectedProgram = [5, "jz", 99, 6, "j", 44];
  63. expect(program).toEqual(expectedProgram);
  64. });
  65. it("handles missing brackets", function () {
  66. expect(function () {
  67. parse("{");
  68. }).toThrow(new Error("Unexpected symbol: found undefined expected 1."));
  69. });
  70. it("handles junk after the end", function () {
  71. const number = 3.3;
  72. const program = parse("{ " + number + " }#");
  73. const expectedProgram = [number];
  74. expect(program).toEqual(expectedProgram);
  75. });
  76. });
  77. describe("PostScriptEvaluator", function () {
  78. function evaluate(program) {
  79. const stream = new StringStream(program);
  80. const parser = new PostScriptParser(new PostScriptLexer(stream));
  81. const code = parser.parse();
  82. const evaluator = new PostScriptEvaluator(code);
  83. const output = evaluator.execute();
  84. return output;
  85. }
  86. it("pushes stack", function () {
  87. const stack = evaluate("{ 99 }");
  88. const expectedStack = [99];
  89. expect(stack).toEqual(expectedStack);
  90. });
  91. it("handles if with true", function () {
  92. const stack = evaluate("{ 1 {99} if }");
  93. const expectedStack = [99];
  94. expect(stack).toEqual(expectedStack);
  95. });
  96. it("handles if with false", function () {
  97. const stack = evaluate("{ 0 {99} if }");
  98. const expectedStack = [];
  99. expect(stack).toEqual(expectedStack);
  100. });
  101. it("handles ifelse with true", function () {
  102. const stack = evaluate("{ 1 {99} {77} ifelse }");
  103. const expectedStack = [99];
  104. expect(stack).toEqual(expectedStack);
  105. });
  106. it("handles ifelse with false", function () {
  107. const stack = evaluate("{ 0 {99} {77} ifelse }");
  108. const expectedStack = [77];
  109. expect(stack).toEqual(expectedStack);
  110. });
  111. it("handles nested if", function () {
  112. const stack = evaluate("{ 1 {1 {77} if} if }");
  113. const expectedStack = [77];
  114. expect(stack).toEqual(expectedStack);
  115. });
  116. it("abs", function () {
  117. const stack = evaluate("{ -2 abs }");
  118. const expectedStack = [2];
  119. expect(stack).toEqual(expectedStack);
  120. });
  121. it("adds", function () {
  122. const stack = evaluate("{ 1 2 add }");
  123. const expectedStack = [3];
  124. expect(stack).toEqual(expectedStack);
  125. });
  126. it("boolean and", function () {
  127. const stack = evaluate("{ true false and }");
  128. const expectedStack = [false];
  129. expect(stack).toEqual(expectedStack);
  130. });
  131. it("bitwise and", function () {
  132. const stack = evaluate("{ 254 1 and }");
  133. const expectedStack = [254 & 1];
  134. expect(stack).toEqual(expectedStack);
  135. });
  136. it("calculates the inverse tangent of a number", function () {
  137. const stack = evaluate("{ 90 atan }");
  138. const expectedStack = [Math.atan(90)];
  139. expect(stack).toEqual(expectedStack);
  140. });
  141. it("handles bitshifting ", function () {
  142. const stack = evaluate("{ 50 2 bitshift }");
  143. const expectedStack = [200];
  144. expect(stack).toEqual(expectedStack);
  145. });
  146. it("calculates the ceiling value", function () {
  147. const stack = evaluate("{ 9.9 ceiling }");
  148. const expectedStack = [10];
  149. expect(stack).toEqual(expectedStack);
  150. });
  151. it("copies", function () {
  152. const stack = evaluate("{ 99 98 2 copy }");
  153. const expectedStack = [99, 98, 99, 98];
  154. expect(stack).toEqual(expectedStack);
  155. });
  156. it("calculates the cosine of a number", function () {
  157. const stack = evaluate("{ 90 cos }");
  158. const expectedStack = [Math.cos(90)];
  159. expect(stack).toEqual(expectedStack);
  160. });
  161. it("converts to int", function () {
  162. const stack = evaluate("{ 9.9 cvi }");
  163. const expectedStack = [9];
  164. expect(stack).toEqual(expectedStack);
  165. });
  166. it("converts negatives to int", function () {
  167. const stack = evaluate("{ -9.9 cvi }");
  168. const expectedStack = [-9];
  169. expect(stack).toEqual(expectedStack);
  170. });
  171. it("converts to real", function () {
  172. const stack = evaluate("{ 55.34 cvr }");
  173. const expectedStack = [55.34];
  174. expect(stack).toEqual(expectedStack);
  175. });
  176. it("divides", function () {
  177. const stack = evaluate("{ 6 5 div }");
  178. const expectedStack = [1.2];
  179. expect(stack).toEqual(expectedStack);
  180. });
  181. it("maps division by zero to infinity", function () {
  182. const stack = evaluate("{ 6 0 div }");
  183. const expectedStack = [Infinity];
  184. expect(stack).toEqual(expectedStack);
  185. });
  186. it("duplicates", function () {
  187. const stack = evaluate("{ 99 dup }");
  188. const expectedStack = [99, 99];
  189. expect(stack).toEqual(expectedStack);
  190. });
  191. it("accepts an equality", function () {
  192. const stack = evaluate("{ 9 9 eq }");
  193. const expectedStack = [true];
  194. expect(stack).toEqual(expectedStack);
  195. });
  196. it("rejects an inequality", function () {
  197. const stack = evaluate("{ 9 8 eq }");
  198. const expectedStack = [false];
  199. expect(stack).toEqual(expectedStack);
  200. });
  201. it("exchanges", function () {
  202. const stack = evaluate("{ 44 99 exch }");
  203. const expectedStack = [99, 44];
  204. expect(stack).toEqual(expectedStack);
  205. });
  206. it("handles exponentiation", function () {
  207. const stack = evaluate("{ 10 2 exp }");
  208. const expectedStack = [100];
  209. expect(stack).toEqual(expectedStack);
  210. });
  211. it("pushes false onto the stack", function () {
  212. const stack = evaluate("{ false }");
  213. const expectedStack = [false];
  214. expect(stack).toEqual(expectedStack);
  215. });
  216. it("calculates the floor value", function () {
  217. const stack = evaluate("{ 9.9 floor }");
  218. const expectedStack = [9];
  219. expect(stack).toEqual(expectedStack);
  220. });
  221. it("handles greater than or equal to", function () {
  222. const stack = evaluate("{ 10 9 ge }");
  223. const expectedStack = [true];
  224. expect(stack).toEqual(expectedStack);
  225. });
  226. it("rejects less than for greater than or equal to", function () {
  227. const stack = evaluate("{ 8 9 ge }");
  228. const expectedStack = [false];
  229. expect(stack).toEqual(expectedStack);
  230. });
  231. it("handles greater than", function () {
  232. const stack = evaluate("{ 10 9 gt }");
  233. const expectedStack = [true];
  234. expect(stack).toEqual(expectedStack);
  235. });
  236. it("rejects less than or equal for greater than", function () {
  237. const stack = evaluate("{ 9 9 gt }");
  238. const expectedStack = [false];
  239. expect(stack).toEqual(expectedStack);
  240. });
  241. it("divides to integer", function () {
  242. const stack = evaluate("{ 2 3 idiv }");
  243. const expectedStack = [0];
  244. expect(stack).toEqual(expectedStack);
  245. });
  246. it("divides to negative integer", function () {
  247. const stack = evaluate("{ -2 3 idiv }");
  248. const expectedStack = [0];
  249. expect(stack).toEqual(expectedStack);
  250. });
  251. it("duplicates index", function () {
  252. const stack = evaluate("{ 4 3 2 1 2 index }");
  253. const expectedStack = [4, 3, 2, 1, 3];
  254. expect(stack).toEqual(expectedStack);
  255. });
  256. it("handles less than or equal to", function () {
  257. const stack = evaluate("{ 9 10 le }");
  258. const expectedStack = [true];
  259. expect(stack).toEqual(expectedStack);
  260. });
  261. it("rejects greater than for less than or equal to", function () {
  262. const stack = evaluate("{ 10 9 le }");
  263. const expectedStack = [false];
  264. expect(stack).toEqual(expectedStack);
  265. });
  266. it("calculates the natural logarithm", function () {
  267. const stack = evaluate("{ 10 ln }");
  268. const expectedStack = [Math.log(10)];
  269. expect(stack).toEqual(expectedStack);
  270. });
  271. it("calculates the base 10 logarithm", function () {
  272. const stack = evaluate("{ 100 log }");
  273. const expectedStack = [2];
  274. expect(stack).toEqual(expectedStack);
  275. });
  276. it("handles less than", function () {
  277. const stack = evaluate("{ 9 10 lt }");
  278. const expectedStack = [true];
  279. expect(stack).toEqual(expectedStack);
  280. });
  281. it("rejects greater than or equal to for less than", function () {
  282. const stack = evaluate("{ 10 9 lt }");
  283. const expectedStack = [false];
  284. expect(stack).toEqual(expectedStack);
  285. });
  286. it("performs the modulo operation", function () {
  287. const stack = evaluate("{ 4 3 mod }");
  288. const expectedStack = [1];
  289. expect(stack).toEqual(expectedStack);
  290. });
  291. it("multiplies two numbers (positive result)", function () {
  292. const stack = evaluate("{ 9 8 mul }");
  293. const expectedStack = [72];
  294. expect(stack).toEqual(expectedStack);
  295. });
  296. it("multiplies two numbers (negative result)", function () {
  297. const stack = evaluate("{ 9 -8 mul }");
  298. const expectedStack = [-72];
  299. expect(stack).toEqual(expectedStack);
  300. });
  301. it("accepts an inequality", function () {
  302. const stack = evaluate("{ 9 8 ne }");
  303. const expectedStack = [true];
  304. expect(stack).toEqual(expectedStack);
  305. });
  306. it("rejects an equality", function () {
  307. const stack = evaluate("{ 9 9 ne }");
  308. const expectedStack = [false];
  309. expect(stack).toEqual(expectedStack);
  310. });
  311. it("negates", function () {
  312. const stack = evaluate("{ 4.5 neg }");
  313. const expectedStack = [-4.5];
  314. expect(stack).toEqual(expectedStack);
  315. });
  316. it("boolean not", function () {
  317. const stack = evaluate("{ true not }");
  318. const expectedStack = [false];
  319. expect(stack).toEqual(expectedStack);
  320. });
  321. it("bitwise not", function () {
  322. const stack = evaluate("{ 12 not }");
  323. const expectedStack = [-13];
  324. expect(stack).toEqual(expectedStack);
  325. });
  326. it("boolean or", function () {
  327. const stack = evaluate("{ true false or }");
  328. const expectedStack = [true];
  329. expect(stack).toEqual(expectedStack);
  330. });
  331. it("bitwise or", function () {
  332. const stack = evaluate("{ 254 1 or }");
  333. const expectedStack = [254 | 1];
  334. expect(stack).toEqual(expectedStack);
  335. });
  336. it("pops stack", function () {
  337. const stack = evaluate("{ 1 2 pop }");
  338. const expectedStack = [1];
  339. expect(stack).toEqual(expectedStack);
  340. });
  341. it("rolls stack right", function () {
  342. const stack = evaluate("{ 1 3 2 2 4 1 roll }");
  343. const expectedStack = [2, 1, 3, 2];
  344. expect(stack).toEqual(expectedStack);
  345. });
  346. it("rolls stack left", function () {
  347. const stack = evaluate("{ 1 3 2 2 4 -1 roll }");
  348. const expectedStack = [3, 2, 2, 1];
  349. expect(stack).toEqual(expectedStack);
  350. });
  351. it("rounds a number", function () {
  352. const stack = evaluate("{ 9.52 round }");
  353. const expectedStack = [10];
  354. expect(stack).toEqual(expectedStack);
  355. });
  356. it("calculates the sine of a number", function () {
  357. const stack = evaluate("{ 90 sin }");
  358. const expectedStack = [Math.sin(90)];
  359. expect(stack).toEqual(expectedStack);
  360. });
  361. it("calculates a square root (integer)", function () {
  362. const stack = evaluate("{ 100 sqrt }");
  363. const expectedStack = [10];
  364. expect(stack).toEqual(expectedStack);
  365. });
  366. it("calculates a square root (float)", function () {
  367. const stack = evaluate("{ 99 sqrt }");
  368. const expectedStack = [Math.sqrt(99)];
  369. expect(stack).toEqual(expectedStack);
  370. });
  371. it("subtracts (positive result)", function () {
  372. const stack = evaluate("{ 6 4 sub }");
  373. const expectedStack = [2];
  374. expect(stack).toEqual(expectedStack);
  375. });
  376. it("subtracts (negative result)", function () {
  377. const stack = evaluate("{ 4 6 sub }");
  378. const expectedStack = [-2];
  379. expect(stack).toEqual(expectedStack);
  380. });
  381. it("pushes true onto the stack", function () {
  382. const stack = evaluate("{ true }");
  383. const expectedStack = [true];
  384. expect(stack).toEqual(expectedStack);
  385. });
  386. it("truncates a number", function () {
  387. const stack = evaluate("{ 35.004 truncate }");
  388. const expectedStack = [35];
  389. expect(stack).toEqual(expectedStack);
  390. });
  391. it("calculates an exclusive or value", function () {
  392. const stack = evaluate("{ 3 9 xor }");
  393. const expectedStack = [10];
  394. expect(stack).toEqual(expectedStack);
  395. });
  396. });
  397. describe("PostScriptCompiler", function () {
  398. function check(code, domain, range, samples) {
  399. const compiler = new PostScriptCompiler();
  400. const compiledCode = compiler.compile(code, domain, range);
  401. if (samples === null) {
  402. expect(compiledCode).toBeNull();
  403. } else {
  404. expect(compiledCode).not.toBeNull();
  405. // eslint-disable-next-line no-new-func
  406. const fn = new Function(
  407. "src",
  408. "srcOffset",
  409. "dest",
  410. "destOffset",
  411. compiledCode
  412. );
  413. for (const { input, output } of samples) {
  414. const out = new Float32Array(output.length);
  415. fn(input, 0, out, 0);
  416. expect(Array.from(out)).toEqual(output);
  417. }
  418. }
  419. }
  420. it("check compiled add", function () {
  421. check([0.25, 0.5, "add"], [], [0, 1], [{ input: [], output: [0.75] }]);
  422. check([0, "add"], [0, 1], [0, 1], [{ input: [0.25], output: [0.25] }]);
  423. check([0.5, "add"], [0, 1], [0, 1], [{ input: [0.25], output: [0.75] }]);
  424. check(
  425. [0, "exch", "add"],
  426. [0, 1],
  427. [0, 1],
  428. [{ input: [0.25], output: [0.25] }]
  429. );
  430. check(
  431. [0.5, "exch", "add"],
  432. [0, 1],
  433. [0, 1],
  434. [{ input: [0.25], output: [0.75] }]
  435. );
  436. check(
  437. ["add"],
  438. [0, 1, 0, 1],
  439. [0, 1],
  440. [{ input: [0.25, 0.5], output: [0.75] }]
  441. );
  442. check(["add"], [0, 1], [0, 1], null);
  443. });
  444. it("check compiled sub", function () {
  445. check([0.5, 0.25, "sub"], [], [0, 1], [{ input: [], output: [0.25] }]);
  446. check([0, "sub"], [0, 1], [0, 1], [{ input: [0.25], output: [0.25] }]);
  447. check([0.5, "sub"], [0, 1], [0, 1], [{ input: [0.75], output: [0.25] }]);
  448. check(
  449. [0, "exch", "sub"],
  450. [0, 1],
  451. [-1, 1],
  452. [{ input: [0.25], output: [-0.25] }]
  453. );
  454. check(
  455. [0.75, "exch", "sub"],
  456. [0, 1],
  457. [-1, 1],
  458. [{ input: [0.25], output: [0.5] }]
  459. );
  460. check(
  461. ["sub"],
  462. [0, 1, 0, 1],
  463. [-1, 1],
  464. [{ input: [0.25, 0.5], output: [-0.25] }]
  465. );
  466. check(["sub"], [0, 1], [0, 1], null);
  467. check(
  468. [1, "dup", 3, 2, "roll", "sub", "sub"],
  469. [0, 1],
  470. [0, 1],
  471. [{ input: [0.75], output: [0.75] }]
  472. );
  473. });
  474. it("check compiled mul", function () {
  475. check([0.25, 0.5, "mul"], [], [0, 1], [{ input: [], output: [0.125] }]);
  476. check([0, "mul"], [0, 1], [0, 1], [{ input: [0.25], output: [0] }]);
  477. check([0.5, "mul"], [0, 1], [0, 1], [{ input: [0.25], output: [0.125] }]);
  478. check([1, "mul"], [0, 1], [0, 1], [{ input: [0.25], output: [0.25] }]);
  479. check(
  480. [0, "exch", "mul"],
  481. [0, 1],
  482. [0, 1],
  483. [{ input: [0.25], output: [0] }]
  484. );
  485. check(
  486. [0.5, "exch", "mul"],
  487. [0, 1],
  488. [0, 1],
  489. [{ input: [0.25], output: [0.125] }]
  490. );
  491. check(
  492. [1, "exch", "mul"],
  493. [0, 1],
  494. [0, 1],
  495. [{ input: [0.25], output: [0.25] }]
  496. );
  497. check(
  498. ["mul"],
  499. [0, 1, 0, 1],
  500. [0, 1],
  501. [{ input: [0.25, 0.5], output: [0.125] }]
  502. );
  503. check(["mul"], [0, 1], [0, 1], null);
  504. });
  505. it("check compiled max", function () {
  506. check(
  507. ["dup", 0.75, "gt", 7, "jz", "pop", 0.75],
  508. [0, 1],
  509. [0, 1],
  510. [{ input: [0.5], output: [0.5] }]
  511. );
  512. check(
  513. ["dup", 0.75, "gt", 7, "jz", "pop", 0.75],
  514. [0, 1],
  515. [0, 1],
  516. [{ input: [1], output: [0.75] }]
  517. );
  518. check(["dup", 0.75, "gt", 5, "jz", "pop", 0.75], [0, 1], [0, 1], null);
  519. });
  520. it("check pop/roll/index", function () {
  521. check([1, "pop"], [0, 1], [0, 1], [{ input: [0.5], output: [0.5] }]);
  522. check(
  523. [1, 3, -1, "roll"],
  524. [0, 1, 0, 1],
  525. [0, 1, 0, 1, 0, 1],
  526. [{ input: [0.25, 0.5], output: [0.5, 1, 0.25] }]
  527. );
  528. check(
  529. [1, 3, 1, "roll"],
  530. [0, 1, 0, 1],
  531. [0, 1, 0, 1, 0, 1],
  532. [{ input: [0.25, 0.5], output: [1, 0.25, 0.5] }]
  533. );
  534. check([1, 3, 1.5, "roll"], [0, 1, 0, 1], [0, 1, 0, 1, 0, 1], null);
  535. check(
  536. [1, 1, "index"],
  537. [0, 1],
  538. [0, 1, 0, 1, 0, 1],
  539. [{ input: [0.5], output: [0.5, 1, 0.5] }]
  540. );
  541. check([1, 3, "index", "pop"], [0, 1], [0, 1], null);
  542. check([1, 0.5, "index", "pop"], [0, 1], [0, 1], null);
  543. });
  544. it("check input boundaries", function () {
  545. check([], [0, 0.5], [0, 1], [{ input: [1], output: [0.5] }]);
  546. check([], [0.5, 1], [0, 1], [{ input: [0], output: [0.5] }]);
  547. check(
  548. ["dup"],
  549. [0.5, 0.75],
  550. [0, 1, 0, 1],
  551. [{ input: [0], output: [0.5, 0.5] }]
  552. );
  553. check([], [100, 1001], [0, 10000], [{ input: [1000], output: [1000] }]);
  554. });
  555. it("check output boundaries", function () {
  556. check([], [0, 1], [0, 0.5], [{ input: [1], output: [0.5] }]);
  557. check([], [0, 1], [0.5, 1], [{ input: [0], output: [0.5] }]);
  558. check(
  559. ["dup"],
  560. [0, 1],
  561. [0.5, 1, 0.75, 1],
  562. [{ input: [0], output: [0.5, 0.75] }]
  563. );
  564. check([], [0, 10000], [100, 1001], [{ input: [1000], output: [1000] }]);
  565. });
  566. it("compile optimized", function () {
  567. const compiler = new PostScriptCompiler();
  568. const code = [0, "add", 1, 1, 3, -1, "roll", "sub", "sub", 1, "mul"];
  569. const compiledCode = compiler.compile(code, [0, 1], [0, 1]);
  570. expect(compiledCode).toEqual(
  571. "dest[destOffset + 0] = Math.max(0, Math.min(1, src[srcOffset + 0]));"
  572. );
  573. });
  574. });
  575. });