2
0

function_spec.js 20 KB

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