AtCoder Easy Test v2

Make testing sample cases easy

נכון ליום 01-10-2021. ראה הגרסה האחרונה.

  1. // ==UserScript==
  2. // @name AtCoder Easy Test v2
  3. // @namespace https://atcoder.jp/
  4. // @version 2.3.1
  5. // @description Make testing sample cases easy
  6. // @author magurofly
  7. // @license MIT
  8. // @supportURL https://github.com/magurofly/atcoder-easy-test/
  9. // @match https://atcoder.jp/contests/*/tasks/*
  10. // @match https://yukicoder.me/problems/no/*
  11. // @grant unsafeWindow
  12. // ==/UserScript==
  13. (function() {
  14. const codeSaver = {
  15. LIMIT: 10,
  16. get() {
  17. // `json` は、ソースコード文字列またはJSON文字列
  18. let json = unsafeWindow.localStorage.AtCoderEasyTest$lastCode;
  19. let data = [];
  20. try {
  21. if (typeof json == "string") {
  22. data.push(...JSON.parse(json));
  23. }
  24. else {
  25. data = [];
  26. }
  27. }
  28. catch (e) {
  29. data.push({
  30. path: unsafeWindow.localStorage.AtCoderEasyTset$lastPage,
  31. code: json,
  32. });
  33. }
  34. return data;
  35. },
  36. set(data) {
  37. unsafeWindow.localStorage.AtCoderEasyTest$lastCode = JSON.stringify(data);
  38. },
  39. save(code) {
  40. let data = codeSaver.get();
  41. const idx = data.findIndex(({ path }) => path == location.pathname);
  42. if (idx != -1)
  43. data.splice(idx, idx + 1);
  44. data.push({
  45. path: location.pathname,
  46. code,
  47. });
  48. while (data.length > codeSaver.LIMIT)
  49. data.shift();
  50. codeSaver.set(data);
  51. },
  52. restore() {
  53. const data = codeSaver.get();
  54. const idx = data.findIndex(({ path }) => path == location.pathname);
  55. if (idx == -1 || !(data[idx] instanceof Object))
  56. return Promise.reject(`No saved code found for ${location.pathname}`);
  57. return Promise.resolve(data[idx].code);
  58. }
  59. };
  60.  
  61. function similarLangs(targetLang, candidateLangs) {
  62. const [targetName, targetDetail] = targetLang.split(" ", 2);
  63. const selectedLangs = candidateLangs.filter(candidateLang => {
  64. const [name, _] = candidateLang.split(" ", 2);
  65. return name == targetName;
  66. }).map(candidateLang => {
  67. const [_, detail] = candidateLang.split(" ", 2);
  68. return [candidateLang, similarity(detail, targetDetail)];
  69. });
  70. return selectedLangs.sort((a, b) => a[1] - b[1]).map(([lang, _]) => lang);
  71. }
  72. function similarity(s, t) {
  73. const n = s.length, m = t.length;
  74. let dp = new Array(m + 1).fill(0);
  75. for (let i = 0; i < n; i++) {
  76. const dp2 = new Array(m + 1).fill(0);
  77. for (let j = 0; j < m; j++) {
  78. const cost = (s.charCodeAt(i) - t.charCodeAt(j)) ** 2;
  79. dp2[j + 1] = Math.min(dp[j] + cost, dp[j + 1] + cost * 0.25, dp2[j] + cost * 0.25);
  80. }
  81. dp = dp2;
  82. }
  83. return dp[m];
  84. }
  85.  
  86. class CodeRunner {
  87. get label() {
  88. return this._label;
  89. }
  90. constructor(label, site) {
  91. this._label = `${label} [${site}]`;
  92. }
  93. async test(sourceCode, input, expectedOutput, options) {
  94. const result = await this.run(sourceCode, input);
  95. if (expectedOutput != null)
  96. result.expectedOutput = expectedOutput;
  97. if (result.status != "OK" || typeof expectedOutput != "string")
  98. return result;
  99. let output = result.output || "";
  100. if (options.trim) {
  101. expectedOutput = expectedOutput.trim();
  102. output = output.trim();
  103. }
  104. let equals = (x, y) => x === y;
  105. if (options.allowableError) {
  106. const floatPattern = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/;
  107. const superEquals = equals;
  108. equals = (x, y) => {
  109. if (floatPattern.test(x) && floatPattern.test(y))
  110. return Math.abs(parseFloat(x) - parseFloat(y)) <= options.allowableError;
  111. return superEquals(x, y);
  112. };
  113. }
  114. if (options.split) {
  115. const superEquals = equals;
  116. equals = (x, y) => {
  117. const xs = x.split(/\s+/);
  118. const ys = y.split(/\s+/);
  119. if (xs.length != ys.length)
  120. return false;
  121. const len = xs.length;
  122. for (let i = 0; i < len; i++) {
  123. if (!superEquals(xs[i], ys[i]))
  124. return false;
  125. }
  126. return true;
  127. };
  128. }
  129. result.status = equals(output, expectedOutput) ? "AC" : "WA";
  130. return result;
  131. }
  132. }
  133.  
  134. class CustomRunner extends CodeRunner {
  135. run;
  136. constructor(label, run) {
  137. super(label, "Browser");
  138. this.run = run;
  139. }
  140. }
  141.  
  142. function buildParams(data) {
  143. return Object.entries(data).map(([key, value]) => encodeURIComponent(key) + "=" + encodeURIComponent(value)).join("&");
  144. }
  145. function sleep(ms) {
  146. return new Promise(done => setTimeout(done, ms));
  147. }
  148. function html2element(html) {
  149. const template = document.createElement("template");
  150. template.innerHTML = html;
  151. return template.content.firstChild;
  152. }
  153. const eventListeners = {};
  154. const events = {
  155. on(name, listener) {
  156. const listeners = (name in eventListeners ? eventListeners[name] : eventListeners[name] = []);
  157. listeners.push(listener);
  158. },
  159. trig(name) {
  160. if (name in eventListeners) {
  161. for (const listener of eventListeners[name])
  162. listener();
  163. }
  164. },
  165. };
  166. class ObservableValue {
  167. _value;
  168. _listeners;
  169. constructor(value) {
  170. this._value = value;
  171. this._listeners = new Set();
  172. }
  173. get value() {
  174. return this._value;
  175. }
  176. set value(value) {
  177. this._value = value;
  178. for (const listener of this._listeners)
  179. listener(value);
  180. }
  181. addListener(listener) {
  182. this._listeners.add(listener);
  183. listener(this._value);
  184. }
  185. removeListener(listener) {
  186. this._listeners.delete(listener);
  187. }
  188. map(f) {
  189. const y = new ObservableValue(f(this.value));
  190. this.addListener(x => {
  191. y.value = f(x);
  192. });
  193. return y;
  194. }
  195. }
  196.  
  197. let waitAtCoderCustomTest = Promise.resolve();
  198. const AtCoderCustomTestBase = location.href.replace(/\/tasks\/.+$/, "/custom_test");
  199. const AtCoderCustomTestResultAPI = AtCoderCustomTestBase + "/json?reload=true";
  200. const AtCoderCustomTestSubmitAPI = AtCoderCustomTestBase + "/submit/json";
  201. class AtCoderRunner extends CodeRunner {
  202. languageId;
  203. constructor(languageId, label) {
  204. super(label, "AtCoder");
  205. this.languageId = languageId;
  206. }
  207. async run(sourceCode, input) {
  208. const promise = this.submit(sourceCode, input);
  209. waitAtCoderCustomTest = promise;
  210. return await promise;
  211. }
  212. async submit(sourceCode, input) {
  213. try {
  214. await waitAtCoderCustomTest;
  215. }
  216. catch (error) {
  217. console.error(error);
  218. }
  219. const error = await fetch(AtCoderCustomTestSubmitAPI, {
  220. method: "POST",
  221. credentials: "include",
  222. headers: {
  223. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
  224. },
  225. body: buildParams({
  226. "data.LanguageId": String(this.languageId),
  227. sourceCode,
  228. input,
  229. csrf_token: unsafeWindow.csrfToken,
  230. }),
  231. }).then(r => r.text());
  232. if (error) {
  233. throw new Error(error);
  234. }
  235. await sleep(100);
  236. for (;;) {
  237. const data = await fetch(AtCoderCustomTestResultAPI, {
  238. method: "GET",
  239. credentials: "include",
  240. }).then(r => r.json());
  241. if (!("Result" in data))
  242. continue;
  243. const result = data.Result;
  244. if ("Interval" in data) {
  245. await sleep(data.Interval);
  246. continue;
  247. }
  248. return {
  249. status: (result.ExitCode == 0) ? "OK" : (result.TimeConsumption == -1) ? "CE" : "RE",
  250. exitCode: result.ExitCode,
  251. execTime: result.TimeConsumption,
  252. memory: result.MemoryConsumption,
  253. input,
  254. output: data.Stdout,
  255. error: data.Stderr,
  256. };
  257. }
  258. }
  259. }
  260.  
  261. class PaizaIORunner extends CodeRunner {
  262. name;
  263. constructor(name, label) {
  264. super(label, "PaizaIO");
  265. this.name = name;
  266. }
  267. async run(sourceCode, input) {
  268. let id, status, error;
  269. try {
  270. const res = await fetch("https://api.paiza.io/runners/create?" + buildParams({
  271. source_code: sourceCode,
  272. language: this.name,
  273. input,
  274. longpoll: "true",
  275. longpoll_timeout: "10",
  276. api_key: "guest",
  277. }), {
  278. method: "POST",
  279. mode: "cors",
  280. }).then(r => r.json());
  281. id = res.id;
  282. status = res.status;
  283. error = res.error;
  284. }
  285. catch (error) {
  286. return {
  287. status: "IE",
  288. input,
  289. error: String(error),
  290. };
  291. }
  292. while (status == "running") {
  293. const res = await fetch("https://api.paiza.io/runners/get_status?" + buildParams({
  294. id,
  295. api_key: "guest",
  296. }), {
  297. mode: "cors",
  298. }).then(res => res.json());
  299. status = res.status;
  300. error = res.error;
  301. }
  302. const res = await fetch("https://api.paiza.io/runners/get_details?" + buildParams({
  303. id,
  304. api_key: "guest",
  305. }), {
  306. mode: "cors",
  307. }).then(r => r.json());
  308. const result = {
  309. status: "OK",
  310. exitCode: String(res.exit_code),
  311. execTime: +res.time * 1e3,
  312. memory: +res.memory * 1e-3,
  313. input,
  314. };
  315. if (res.build_result == "failure") {
  316. result.status = "CE";
  317. result.exitCode = res.build_exit_code;
  318. result.output = res.build_stdout;
  319. result.error = res.build_stderr;
  320. }
  321. else {
  322. result.status = (res.result == "timeout") ? "TLE" : (res.result == "failure") ? "RE" : "OK";
  323. result.exitCode = res.exit_code;
  324. result.output = res.stdout;
  325. result.error = res.stderr;
  326. }
  327. return result;
  328. }
  329. }
  330.  
  331. class WandboxRunner extends CodeRunner {
  332. name;
  333. options;
  334. constructor(name, label, options = {}) {
  335. super(label, "Wandbox");
  336. this.name = name;
  337. this.options = options;
  338. }
  339. getOptions(sourceCode, input) {
  340. if (typeof this.options == "function")
  341. return this.options(sourceCode, input);
  342. return this.options;
  343. }
  344. run(sourceCode, input) {
  345. const options = this.getOptions(sourceCode, input);
  346. return this.request(Object.assign({
  347. compiler: this.name,
  348. code: sourceCode,
  349. stdin: input,
  350. }, options));
  351. }
  352. async request(body) {
  353. const startTime = Date.now();
  354. let res;
  355. try {
  356. res = await fetch("https://wandbox.org/api/compile.json", {
  357. method: "POST",
  358. mode: "cors",
  359. headers: {
  360. "Content-Type": "application/json",
  361. },
  362. body: JSON.stringify(body),
  363. }).then(r => r.json());
  364. }
  365. catch (error) {
  366. console.error(error);
  367. return {
  368. status: "IE",
  369. input: body.stdin,
  370. error: String(error),
  371. };
  372. }
  373. const endTime = Date.now();
  374. const result = {
  375. status: "OK",
  376. exitCode: String(res.status),
  377. execTime: endTime - startTime,
  378. input: body.stdin,
  379. output: String(res.program_output || ""),
  380. error: String(res.program_error || ""),
  381. };
  382. // 正常終了以外の場合
  383. if (res.status != 0) {
  384. if (res.signal) {
  385. result.exitCode += ` (${res.signal})`;
  386. }
  387. result.output = String(res.compiler_output || "") + String(result.output || "");
  388. result.error = String(res.compiler_error || "") + String(result.error || "");
  389. if (res.compiler_output || res.compiler_error) {
  390. result.status = "CE";
  391. }
  392. else {
  393. result.status = "RE";
  394. }
  395. }
  396. return result;
  397. }
  398. }
  399.  
  400. class WandboxCppRunner extends WandboxRunner {
  401. async run(sourceCode, input) {
  402. // ACL を結合する
  403. const ACLBase = "https://cdn.jsdelivr.net/gh/atcoder/ac-library/";
  404. const files = new Map();
  405. const includeHeader = async (source) => {
  406. const pattern = /^#\s*include\s*[<"]atcoder\/([^>"]+)[>"]/gm;
  407. const loaded = [];
  408. let match;
  409. while (match = pattern.exec(source)) {
  410. const file = "atcoder/" + match[1];
  411. if (files.has(file))
  412. continue;
  413. files.set(file, null);
  414. loaded.push([file, fetch(ACLBase + file, { mode: "cors", cache: "force-cache", }).then(r => r.text())]);
  415. }
  416. const included = await Promise.all(loaded.map(async ([file, r]) => {
  417. const source = await r;
  418. files.set(file, source);
  419. return source;
  420. }));
  421. for (const source of included) {
  422. await includeHeader(source);
  423. }
  424. };
  425. await includeHeader(sourceCode);
  426. const codes = [];
  427. for (const [file, code] of files) {
  428. codes.push({ file, code, });
  429. }
  430. const options = this.getOptions(sourceCode, input);
  431. return await this.request(Object.assign({
  432. compiler: this.name,
  433. code: sourceCode,
  434. stdin: input,
  435. codes,
  436. "compiler-option-raw": "-I.",
  437. }, options));
  438. }
  439. }
  440.  
  441. let brythonRunnerLoaded = false;
  442. const brythonRunner = new CustomRunner("Brython", async (sourceCode, input) => {
  443. if (!brythonRunnerLoaded) {
  444. // BrythonRunner を読み込む
  445. await new Promise((resolve) => {
  446. const script = document.createElement("script");
  447. script.src = "https://cdn.jsdelivr.net/gh/pythonpad/brython-runner/lib/brython-runner.bundle.js";
  448. script.onload = () => {
  449. brythonRunnerLoaded = true;
  450. resolve(null);
  451. };
  452. document.head.appendChild(script);
  453. });
  454. }
  455. let stdout = "";
  456. let stderr = "";
  457. let stdinOffset = 0;
  458. const BrythonRunner = unsafeWindow.BrythonRunner;
  459. const runner = new BrythonRunner({
  460. stdout: { write(content) { stdout += content; }, flush() { } },
  461. stderr: { write(content) { stderr += content; }, flush() { } },
  462. stdin: { async readline() {
  463. let index = input.indexOf("\n", stdinOffset) + 1;
  464. if (index == 0)
  465. index = input.length;
  466. const text = input.slice(stdinOffset, index);
  467. stdinOffset = index;
  468. return text;
  469. } },
  470. });
  471. const timeStart = Date.now();
  472. await runner.runCode(sourceCode);
  473. const timeEnd = Date.now();
  474. return {
  475. status: "OK",
  476. exitCode: "0",
  477. execTime: (timeEnd - timeStart),
  478. input,
  479. output: stdout,
  480. error: stderr,
  481. };
  482. });
  483.  
  484. let atcoder = null;
  485. function pairs(list) {
  486. const pairs = [];
  487. const len = list.length >> 1;
  488. for (let i = 0; i < len; i++)
  489. pairs.push([list[i * 2], list[i * 2 + 1]]);
  490. return pairs;
  491. }
  492. function init$1() {
  493. const doc = unsafeWindow.document;
  494. const eLanguage = unsafeWindow.$("#select-lang>select");
  495. const langMap = {
  496. 4001: "C GCC 9.2.1",
  497. 4002: "C Clang 10.0.0",
  498. 4003: "C++ GCC 9.2.1",
  499. 4004: "C++ Clang 10.0.0",
  500. 4005: "Java OpenJDK 11.0.6",
  501. 4006: "Python3 CPython 3.8.2",
  502. 4007: "Bash 5.0.11",
  503. 4008: "bc 1.07.1",
  504. 4009: "Awk GNU Awk 4.1.4",
  505. 4010: "C# .NET Core 3.1.201",
  506. 4011: "C# Mono-mcs 6.8.0.105",
  507. 4012: "C# Mono-csc 3.5.0",
  508. 4013: "Clojure 1.10.1.536",
  509. 4014: "Crystal 0.33.0",
  510. 4015: "D DMD 2.091.0",
  511. 4016: "D GDC 9.2.1",
  512. 4017: "D LDC 1.20.1",
  513. 4018: "Dart 2.7.2",
  514. 4019: "dc 1.4.1",
  515. 4020: "Erlang 22.3",
  516. 4021: "Elixir 1.10.2",
  517. 4022: "F# .NET Core 3.1.201",
  518. 4023: "F# Mono 10.2.3",
  519. 4024: "Forth gforth 0.7.3",
  520. 4025: "Fortran GNU Fortran 9.2.1",
  521. 4026: "Go 1.14.1",
  522. 4027: "Haskell GHC 8.8.3",
  523. 4028: "Haxe 4.0.3",
  524. 4029: "Haxe 4.0.3",
  525. 4030: "JavaScript Node.js 12.16.1",
  526. 4031: "Julia 1.4.0",
  527. 4032: "Kotlin 1.3.71",
  528. 4033: "Lua Lua 5.3.5",
  529. 4034: "Lua LuaJIT 2.1.0",
  530. 4035: "Dash 0.5.8",
  531. 4036: "Nim 1.0.6",
  532. 4037: "Objective-C Clang 10.0.0",
  533. 4038: "Lisp SBCL 2.0.3",
  534. 4039: "OCaml 4.10.0",
  535. 4040: "Octave 5.2.0",
  536. 4041: "Pascal FPC 3.0.4",
  537. 4042: "Perl 5.26.1",
  538. 4043: "Raku Rakudo 2020.02.1",
  539. 4044: "PHP 7.4.4",
  540. 4045: "Prolog SWI-Prolog 8.0.3",
  541. 4046: "Python PyPy2 7.3.0",
  542. 4047: "Python3 PyPy3 7.3.0",
  543. 4048: "Racket 7.6",
  544. 4049: "Ruby 2.7.1",
  545. 4050: "Rust 1.42.0",
  546. 4051: "Scala 2.13.1",
  547. 4052: "Java OpenJDK 1.8.0",
  548. 4053: "Scheme Gauche 0.9.9",
  549. 4054: "ML MLton 20130715",
  550. 4055: "Swift 5.2.1",
  551. 4056: "Text cat 8.28",
  552. 4057: "TypeScript 3.8",
  553. 4058: "Basic .NET Core 3.1.101",
  554. 4059: "Zsh 5.4.2",
  555. 4060: "COBOL Fixed OpenCOBOL 1.1.0",
  556. 4061: "COBOL Free OpenCOBOL 1.1.0",
  557. 4062: "Brainfuck bf 20041219",
  558. 4063: "Ada Ada2012 GNAT 9.2.1",
  559. 4064: "Unlambda 2.0.0",
  560. 4065: "Cython 0.29.16",
  561. 4066: "Sed 4.4",
  562. 4067: "Vim 8.2.0460",
  563. };
  564. const languageId = new ObservableValue(eLanguage.val());
  565. eLanguage.change(() => {
  566. languageId.value = eLanguage.val();
  567. });
  568. const language = languageId.map(lang => langMap[lang]);
  569. function getTestCases() {
  570. const selectors = [
  571. ["#task-statement p+pre.literal-block", ".section"],
  572. ["#task-statement pre.source-code-for-copy", ".part"],
  573. ["#task-statement .lang>*:nth-child(1) .div-btn-copy+pre", ".part"],
  574. ["#task-statement .div-btn-copy+pre", ".part"],
  575. ["#task-statement>.part pre.linenums", ".part"],
  576. ["#task-statement>.part:not(.io-style)>h3+section>pre", ".part"],
  577. ["#task-statement pre", ".part"],
  578. ];
  579. for (const [selector, closestSelector] of selectors) {
  580. let e = [...doc.querySelectorAll(selector)];
  581. e = e.filter(e => !e.closest(".io-style")); // practice2
  582. if (e.length == 0)
  583. continue;
  584. return pairs(e).map(([input, output], index) => ({
  585. title: `Sample ${index + 1}`,
  586. input: input.textContent,
  587. output: output.textContent,
  588. anchor: (input.closest(closestSelector) || input.parentElement).querySelector(".btn-copy"),
  589. }));
  590. }
  591. { // maximum_cup_2018_d
  592. let e = [...doc.querySelectorAll("#task-statement .div-btn-copy+pre")];
  593. e = e.filter(f => !f.childElementCount);
  594. if (e.length) {
  595. return pairs(e).map(([input, output], index) => ({
  596. title: `Sample ${index + 1}`,
  597. input: input.textContent,
  598. output: output.textContent,
  599. anchor: (input.closest(".part") || input.parentElement).querySelector(".btn-copy"),
  600. }));
  601. }
  602. }
  603. return [];
  604. }
  605. atcoder = {
  606. name: "AtCoder",
  607. language,
  608. get sourceCode() {
  609. return unsafeWindow.getSourceCode();
  610. },
  611. set sourceCode(sourceCode) {
  612. doc.querySelector(".plain-textarea").value = sourceCode;
  613. unsafeWindow.$(".editor").data("editor").doc.setValue(sourceCode);
  614. },
  615. submit() {
  616. doc.querySelector("#submit").click();
  617. },
  618. get testButtonContainer() {
  619. return doc.querySelector("#submit").parentElement;
  620. },
  621. get sideButtonContainer() {
  622. return doc.querySelector(".editor-buttons");
  623. },
  624. get bottomMenuContainer() {
  625. return doc.getElementById("main-div");
  626. },
  627. get resultListContainer() {
  628. return doc.querySelector(".form-code-submit");
  629. },
  630. get testCases() {
  631. return getTestCases();
  632. }
  633. };
  634. }
  635. if (location.host == "atcoder.jp")
  636. init$1();
  637. var atcoder$1 = atcoder;
  638.  
  639. let yukicoder = null;
  640. function init() {
  641. const $ = unsafeWindow.$;
  642. const doc = unsafeWindow.document;
  643. const editor = unsafeWindow.ace.edit("rich_source");
  644. const eSourceObject = $("#source");
  645. const eLang = $("#lang");
  646. const eSamples = $(".sample");
  647. const langMap = {
  648. "cpp14": "C++ C++14 GCC 11.1.0 + Boost 1.77.0",
  649. "cpp17": "C++ C++17 GCC 11.1.0 + Boost 1.77.0",
  650. "cpp-clang": "C++ C++17 Clang 10.0.0 + Boost 1.76.0",
  651. "cpp23": "C++ C++11 GCC 8.4.1",
  652. "c11": "C++ C++11 GCC 11.1.0",
  653. "c": "C C90 GCC 8.4.1",
  654. "java8": "Java Java16 OpenJDK 16.0.1",
  655. "csharp": "C# CSC 3.9.0",
  656. "csharp_mono": "C# Mono 6.12.0.147",
  657. "csharp_dotnet": "C# .NET 5.0",
  658. "perl": "Perl 5.26.3",
  659. "raku": "Raku Rakudo v2021-07-2-g74d7ff771",
  660. "php": "PHP 7.2.24",
  661. "php7": "PHP 8.0.8",
  662. "python3": "Python3 3.9.6 + numpy 1.14.5 + scipy 1.1.0",
  663. "pypy2": "Python PyPy2 7.3.5",
  664. "pypy3": "Python3 PyPy3 7.3.5",
  665. "ruby": "Ruby 3.0.2p107",
  666. "d": "D DMD 2.097.1",
  667. "go": "Go 1.16.6",
  668. "haskell": "Haskell 8.10.5",
  669. "scala": "Scala 2.13.6",
  670. "nim": "Nim 1.4.8",
  671. "rust": "Rust 1.53.0",
  672. "kotlin": "Kotlin 1.5.21",
  673. "scheme": "Scheme Gauche 0.9.10",
  674. "crystal": "Crystal 1.1.1",
  675. "swift": "Swift 5.4.2",
  676. "ocaml": "OCaml 4.12.0",
  677. "clojure": "Clojure 1.10.2.790",
  678. "fsharp": "F# 5.0",
  679. "elixir": "Elixir 1.7.4",
  680. "lua": "Lua LuaJIT 2.0.5",
  681. "fortran": "Fortran gFortran 8.4.1",
  682. "node": "JavaScript Node.js 15.5.0",
  683. "typescript": "TypeScript 4.3.5",
  684. "lisp": "Lisp Common Lisp sbcl 2.1.6",
  685. "sml": "ML Standard ML MLton 20180207-6",
  686. "kuin": "Kuin KuinC++ v.2021.7.17",
  687. "vim": "Vim v8.2",
  688. "sh": "Bash 4.4.19",
  689. "nasm": "Assembler nasm 2.13.03",
  690. "clay": "cLay 20210917-1",
  691. "bf": "Brainfuck BFI 1.1",
  692. "Whitespace": "Whitespace 0.3",
  693. "text": "Text cat 8.3",
  694. };
  695. const language = new ObservableValue(langMap[eLang.val()]);
  696. eLang.on("change", () => {
  697. language.value = langMap[eLang.val()];
  698. });
  699. yukicoder = {
  700. name: "yukicoder",
  701. language,
  702. get sourceCode() {
  703. if (eSourceObject.is(":visible"))
  704. return eSourceObject.val();
  705. return editor.getSession().getValue();
  706. },
  707. set sourceCode(sourceCode) {
  708. eSourceObject.val(sourceCode);
  709. editor.getSession().setValue(sourceCode);
  710. },
  711. submit() {
  712. doc.querySelector(`#submit_form input[type="submit"]`).click();
  713. },
  714. get testButtonContainer() {
  715. return doc.querySelector("#submit_form");
  716. },
  717. get sideButtonContainer() {
  718. return doc.querySelector("#toggle_source_editor").parentElement;
  719. },
  720. get bottomMenuContainer() {
  721. return doc.body;
  722. },
  723. get resultListContainer() {
  724. return doc.querySelector("#content");
  725. },
  726. get testCases() {
  727. const testCases = [];
  728. let sampleId = 1;
  729. for (let i = 0; i < eSamples.length; i++) {
  730. const eSample = eSamples.eq(i);
  731. const [eInput, eOutput] = eSample.find("pre");
  732. testCases.push({
  733. title: `Sample ${sampleId++}`,
  734. input: eInput.textContent,
  735. output: eOutput.textContent,
  736. anchor: eSample.find("button")[0],
  737. });
  738. }
  739. return testCases;
  740. },
  741. };
  742. }
  743. if (location.host == "yukicoder.me")
  744. init();
  745. var yukicoder$1 = yukicoder;
  746.  
  747. const site = atcoder$1 || yukicoder$1;
  748.  
  749. const runners = {
  750. "C GCC 10.1.0 Wandbox": new WandboxRunner("gcc-10.1.0-c", "C (GCC 10.1.0)"),
  751. "C C17 Clang 10.0.0 paiza.io": new PaizaIORunner("c", "C (C17 / Clang 10.0.0)"),
  752. "C++ GCC 10.1.0 + Boost 1.73.0 + ACL Wandbox": new WandboxCppRunner("gcc-10.1.0", "C++ (GCC 10.1.0) + ACL", { options: "warning,boost-1.73.0-gcc-9.2.0,gnu++17" }),
  753. "C++ Clang 10.0.0 + ACL Wandbox": new WandboxCppRunner("clang-10.0.0", "C++ (Clang 10.0.0) + ACL", { options: "warning,boost-nothing-clang-10.0.0,c++17" }),
  754. "Python3 CPython 3.8.2 paiza.io": new PaizaIORunner("python3", "Python (3.8.2)"),
  755. "Python3 Brython": brythonRunner,
  756. "Bash 5.0.17 paiza.io": new PaizaIORunner("bash", "Bash (5.0.17)"),
  757. "C# .NET Core 6.0.100-alpha.1.20562.2 Wandbox": new WandboxRunner("csharp", "C# (.NET Core 6.0.100-alpha.1.20562.2)"),
  758. "C# Mono-mcs HEAD Wandbox": new WandboxRunner("mono-head", "C# (Mono-mcs HEAD)"),
  759. "Clojure 1.10.1-1 paiza.io": new PaizaIORunner("clojure", "Clojure (1.10.1-1)"),
  760. "D LDC 1.23.0 paiza.io": new PaizaIORunner("d", "D (LDC 1.23.0)"),
  761. "Erlang 10.6.4 paiza.io": new PaizaIORunner("erlang", "Erlang (10.6.4)"),
  762. "Elixir 1.10.4 paiza.io": new PaizaIORunner("elixir", "Elixir (1.10.4)"),
  763. "F# Interactive 4.0 paiza.io": new PaizaIORunner("fsharp", "F# (Interactive 4.0)"),
  764. "Go 1.14.1 Wandbox": new WandboxRunner("go-1.14.1", "Go (1.14.1)"),
  765. "Haskell GHC HEAD Wandbox": new WandboxRunner("ghc-head", "Haskell (GHC HEAD)"),
  766. "JavaScript Node.js paiza.io": new PaizaIORunner("javascript", "JavaScript (Node.js 12.18.3)"),
  767. "Kotlin 1.4.0 paiza.io": new PaizaIORunner("kotlin", "Kotlin (1.4.0)"),
  768. "Lua 5.3.4 Wandbox": new WandboxRunner("lua-5.3.4", "Lua (Lua 5.3.4)"),
  769. "Lua LuaJIT HEAD Wandbox": new WandboxRunner("luajit-head", "Lua (LuaJIT HEAD)"),
  770. "Nim 1.0.6 Wandbox": new WandboxRunner("nim-1.0.6", "Nim (1.0.6)"),
  771. "Objective-C Clang 10.0.0 paiza.io": new PaizaIORunner("objective-c", "Objective-C (Clang 10.0.0)"),
  772. "Ocaml HEAD Wandbox": new WandboxRunner("ocaml-head", "OCaml (HEAD)"),
  773. "Pascal FPC 3.0.2 Wandbox": new WandboxRunner("fpc-3.0.2", "Pascal (FPC 3.0.2)"),
  774. "Perl 5.30.0 paiza.io": new PaizaIORunner("perl", "Perl (5.30.0)"),
  775. "PHP 7.4.10 paiza.io": new PaizaIORunner("php", "PHP (7.4.10)"),
  776. "PHP 7.3.3 Wandbox": new WandboxRunner("php-7.3.3", "PHP (7.3.3)"),
  777. "Python PyPy HEAD Wandbox": new WandboxRunner("pypy-head", "PyPy2 (HEAD)"),
  778. "Python3 PyPy3 7.2.0 Wandbox": new WandboxRunner("pypy-7.2.0-3", "PyPy3 (7.2.0)"),
  779. "Ruby 2.7.1 paiza.io": new PaizaIORunner("ruby", "Ruby (2.7.1)"),
  780. "Ruby HEAD Wandbox": new WandboxRunner("ruby-head", "Ruby (HEAD)"),
  781. "Ruby 2.7.1 Wandbox": new WandboxRunner("ruby-2.7.1", "Ruby (2.7.1)"),
  782. "Rust 1.42.0 AtCoder": new AtCoderRunner("4050", "Rust (1.42.0)"),
  783. "Rust HEAD Wandbox": new WandboxRunner("rust-head", "Rust (HEAD)"),
  784. "Rust 1.43.0 paiza.io": new PaizaIORunner("rust", "Rust (1.43.0)"),
  785. "Scala 2.13.3 paiza": new PaizaIORunner("scala", "Scala (2.13.3)"),
  786. "Scheme Gauche 0.9.6 paiza.io": new PaizaIORunner("scheme", "Scheme (Gauche 0.9.6)"),
  787. "Swift 5.2.5 paiza.io": new PaizaIORunner("swift", "Swift (5.2.5)"),
  788. "Text local": new CustomRunner("Text", async (sourceCode, input) => {
  789. return {
  790. status: "OK",
  791. exitCode: "0",
  792. input,
  793. output: sourceCode,
  794. };
  795. }),
  796. "Basic Visual Basic .NET Core 4.0.1 paiza.io": new PaizaIORunner("vb", "Visual Basic (.NET Core 4.0.1)"),
  797. "COBOL Free OpenCOBOL 2.2.0 paiza.io": new PaizaIORunner("cobol", "COBOL - Free (OpenCOBOL 2.2.0)"),
  798. "COBOL Fixed OpenCOBOL 1.1.0 AtCoder": new AtCoderRunner("4060", "COBOL - Fixed (OpenCOBOL 1.1.0)"),
  799. "COBOL Free OpenCOBOL 1.1.0 AtCoder": new AtCoderRunner("4061", "COBOL - Free (OpenCOBOL 1.1.0)"),
  800. "C++ GCC 9.2.0 + ACL Wandbox": new WandboxCppRunner("gcc-9.2.0", "C++ (GCC 9.2.0) + ACL"),
  801. };
  802. if (site.name == "AtCoder") {
  803. // AtCoderRunner がない場合は、追加する
  804. for (const e of document.querySelectorAll("#select-lang option[value]")) {
  805. const m = e.textContent.match(/([^ ]+) \(([^)]+)\)/);
  806. if (m) {
  807. const name = `${m[1]} ${m[2]} AtCoder`;
  808. const languageId = e.value;
  809. runners[name] = new AtCoderRunner(languageId, e.textContent);
  810. }
  811. }
  812. }
  813. console.info("AtCoder Easy Test: codeRunner OK");
  814. var codeRunner = {
  815. // 指定した環境でコードを実行する
  816. run(languageId, sourceCode, input, expectedOutput, options = { trim: true, split: true }) {
  817. // CodeRunner が存在しない言語ID
  818. if (!(languageId in runners))
  819. return Promise.reject("Language not supported");
  820. // 最後に実行したコードを保存
  821. if (sourceCode.length > 0)
  822. codeSaver.save(sourceCode);
  823. // 実行
  824. return runners[languageId].test(sourceCode, input, expectedOutput, options);
  825. },
  826. // 環境の名前の一覧を取得する
  827. async getEnvironment(languageId) {
  828. const langs = similarLangs(languageId, Object.keys(runners));
  829. if (langs.length == 0)
  830. throw `Undefined language: ${languageId}`;
  831. return langs.map(lang => [lang, runners[lang].label]);
  832. },
  833. };
  834.  
  835. var hBottomMenu = "<div id=\"bottom-menu-wrapper\" class=\"navbar navbar-default navbar-fixed-bottom\">\n <div class=\"container\">\n <div class=\"navbar-header\">\n <button id=\"bottom-menu-key\" type=\"button\" class=\"navbar-toggle collapsed glyphicon glyphicon-menu-down\" data-toggle=\"collapse\" data-target=\"#bottom-menu\"></button>\n </div>\n <div id=\"bottom-menu\" class=\"collapse navbar-collapse\">\n <ul id=\"bottom-menu-tabs\" class=\"nav nav-tabs\"></ul>\n <div id=\"bottom-menu-contents\" class=\"tab-content\"></div>\n </div>\n </div>\n</div>";
  836.  
  837. var hStyle$1 = "<style>\n#bottom-menu-wrapper {\n background: transparent;\n border: none;\n pointer-events: none;\n padding: 0;\n}\n\n#bottom-menu-wrapper>.container {\n position: absolute;\n bottom: 0;\n width: 100%;\n padding: 0;\n}\n\n#bottom-menu-wrapper>.container>.navbar-header {\n float: none;\n}\n\n#bottom-menu-key {\n display: block;\n float: none;\n margin: 0 auto;\n padding: 10px 3em;\n border-radius: 5px 5px 0 0;\n background: #000;\n opacity: 0.5;\n color: #FFF;\n cursor: pointer;\n pointer-events: auto;\n text-align: center;\n}\n\n@media screen and (max-width: 767px) {\n #bottom-menu-key {\n opacity: 0.25;\n }\n}\n\n#bottom-menu-key.collapsed:before {\n content: \"\\e260\";\n}\n\n#bottom-menu-tabs {\n padding: 3px 0 0 10px;\n cursor: n-resize;\n}\n\n#bottom-menu-tabs a {\n pointer-events: auto;\n}\n\n#bottom-menu {\n pointer-events: auto;\n background: rgba(0, 0, 0, 0.8);\n color: #fff;\n max-height: unset;\n}\n\n#bottom-menu.collapse:not(.in) {\n display: none !important;\n}\n\n#bottom-menu-tabs>li>a {\n background: rgba(150, 150, 150, 0.5);\n color: #000;\n border: solid 1px #ccc;\n filter: brightness(0.75);\n}\n\n#bottom-menu-tabs>li>a:hover {\n background: rgba(150, 150, 150, 0.5);\n border: solid 1px #ccc;\n color: #111;\n filter: brightness(0.9);\n}\n\n#bottom-menu-tabs>li.active>a {\n background: #eee;\n border: solid 1px #ccc;\n color: #333;\n filter: none;\n}\n\n.bottom-menu-btn-close {\n font-size: 8pt;\n vertical-align: baseline;\n padding: 0 0 0 6px;\n margin-right: -6px;\n}\n\n#bottom-menu-contents {\n padding: 5px 15px;\n max-height: 50vh;\n overflow-y: auto;\n}\n\n#bottom-menu-contents .panel {\n color: #333;\n}\n</style>";
  838.  
  839. const style = html2element(hStyle$1);
  840. const bottomMenu = html2element(hBottomMenu);
  841. unsafeWindow.document.head.appendChild(style);
  842. site.bottomMenuContainer.appendChild(bottomMenu);
  843. const bottomMenuKey = bottomMenu.querySelector("#bottom-menu-key");
  844. const bottomMenuTabs = bottomMenu.querySelector("#bottom-menu-tabs");
  845. const bottomMenuContents = bottomMenu.querySelector("#bottom-menu-contents");
  846. // メニューのリサイズ
  847. {
  848. let resizeStart = null;
  849. const onStart = (event) => {
  850. const target = event.target;
  851. const pageY = event.pageY;
  852. if (target.id != "bottom-menu-tabs")
  853. return;
  854. resizeStart = { y: pageY, height: bottomMenuContents.getBoundingClientRect().height };
  855. };
  856. const onMove = (event) => {
  857. if (!resizeStart)
  858. return;
  859. event.preventDefault();
  860. bottomMenuContents.style.height = `${resizeStart.height - (event.pageY - resizeStart.y)}px`;
  861. };
  862. const onEnd = () => {
  863. resizeStart = null;
  864. };
  865. bottomMenuTabs.addEventListener("mousedown", onStart);
  866. bottomMenuTabs.addEventListener("mousemove", onMove);
  867. bottomMenuTabs.addEventListener("mouseup", onEnd);
  868. bottomMenuTabs.addEventListener("mouseleave", onEnd);
  869. }
  870. let tabs = new Set();
  871. let selectedTab = null;
  872. /** 下メニューの操作 */
  873. const menuController = {
  874. /** タブを選択 */
  875. selectTab(tabId) {
  876. const tab = unsafeWindow.$(`#bottom-menu-tab-${tabId}`);
  877. if (tab && tab[0]) {
  878. tab.tab("show"); // Bootstrap 3
  879. selectedTab = tabId;
  880. }
  881. },
  882. /** 下メニューにタブを追加する */
  883. addTab(tabId, tabLabel, paneContent, options = {}) {
  884. console.log(`AtCoder Easy Test: addTab: ${tabLabel} (${tabId})`, paneContent);
  885. // タブを追加
  886. const tab = document.createElement("a");
  887. tab.textContent = tabLabel;
  888. tab.id = `bottom-menu-tab-${tabId}`;
  889. tab.href = "#";
  890. tab.dataset.target = `#bottom-menu-pane-${tabId}`;
  891. tab.dataset.toggle = "tab";
  892. tab.addEventListener("click", event => {
  893. event.preventDefault();
  894. menuController.selectTab(tabId);
  895. });
  896. const tabLi = document.createElement("li");
  897. tabLi.appendChild(tab);
  898. bottomMenuTabs.appendChild(tabLi);
  899. // 内容を追加
  900. const pane = document.createElement("div");
  901. pane.className = "tab-pane";
  902. pane.id = `bottom-menu-pane-${tabId}`;
  903. pane.appendChild(paneContent);
  904. bottomMenuContents.appendChild(pane);
  905. const controller = {
  906. get id() {
  907. return tabId;
  908. },
  909. close() {
  910. bottomMenuTabs.removeChild(tabLi);
  911. bottomMenuContents.removeChild(pane);
  912. tabs.delete(tab);
  913. if (selectedTab == tabId) {
  914. selectedTab = null;
  915. if (tabs.size > 0) {
  916. menuController.selectTab(tabs.values().next().value.id);
  917. }
  918. }
  919. },
  920. show() {
  921. menuController.show();
  922. menuController.selectTab(tabId);
  923. },
  924. set color(color) {
  925. tab.style.backgroundColor = color;
  926. },
  927. };
  928. // 選択されているタブがなければ選択
  929. if (!selectedTab)
  930. menuController.selectTab(tabId);
  931. return controller;
  932. },
  933. /** 下メニューを表示する */
  934. show() {
  935. if (bottomMenuKey.classList.contains("collapsed"))
  936. bottomMenuKey.click();
  937. },
  938. /** 下メニューの表示/非表示を切り替える */
  939. toggle() {
  940. bottomMenuKey.click();
  941. },
  942. };
  943. console.info("AtCoder Easy Test: bottomMenu OK");
  944.  
  945. var hRowTemplate = "<div class=\"atcoder-easy-test-cases-row alert alert-dismissible\">\n <button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"close\">\n <span aria-hidden=\"true\">×</span>\n </button>\n <div class=\"progress\">\n <div class=\"progress-bar\" style=\"width: 0%;\">0 / 0</div>\n </div>\n <!--div class=\"label label-default label-warning\" style=\"margin: 3px; cursor: pointer;\">WA</div>\n <div class=\"label label-default label-warning\" style=\"margin: 3px; cursor: pointer;\">WA</div>\n <div class=\"label label-default label-warning\" style=\"margin: 3px; cursor: pointer;\">WA</div-->\n</div>";
  946.  
  947. class ResultRow {
  948. _tabs;
  949. _element;
  950. _promise;
  951. constructor(pairs) {
  952. this._tabs = pairs.map(([_, tab]) => tab);
  953. this._element = html2element(hRowTemplate);
  954. const numCases = pairs.length;
  955. let numFinished = 0;
  956. let numAccepted = 0;
  957. const progressBar = this._element.querySelector(".progress-bar");
  958. progressBar.textContent = `${numFinished} / ${numCases}`;
  959. this._promise = Promise.all(pairs.map(([pResult, tab]) => {
  960. const button = html2element(`<div class="label label-default" style="margin: 3px; cursor: pointer;">WJ</div>`);
  961. button.addEventListener("click", () => {
  962. tab.show();
  963. });
  964. this._element.appendChild(button);
  965. return pResult.then(result => {
  966. button.textContent = result.status;
  967. if (result.status == "AC") {
  968. button.classList.add("label-success");
  969. }
  970. else if (result.status != "OK") {
  971. button.classList.add("label-warning");
  972. }
  973. numFinished++;
  974. if (result.status == "AC")
  975. numAccepted++;
  976. progressBar.textContent = `${numFinished} / ${numCases}`;
  977. progressBar.style.width = `${100 * numFinished / numCases}%`;
  978. if (numFinished == numCases) {
  979. if (numAccepted == numCases)
  980. this._element.classList.add("alert-success");
  981. else
  982. this._element.classList.add("alert-warning");
  983. }
  984. }).catch(reason => {
  985. button.textContent = "IE";
  986. button.classList.add("label-danger");
  987. console.error(reason);
  988. });
  989. }));
  990. }
  991. get element() {
  992. return this._element;
  993. }
  994. onFinish(listener) {
  995. this._promise.then(listener);
  996. }
  997. remove() {
  998. for (const tab of this._tabs)
  999. tab.close();
  1000. const parent = this._element.parentElement;
  1001. if (parent)
  1002. parent.removeChild(this._element);
  1003. }
  1004. }
  1005.  
  1006. var hResultList = "<div class=\"row\"></div>";
  1007.  
  1008. const eResultList = html2element(hResultList);
  1009. site.resultListContainer.appendChild(eResultList);
  1010. const resultList = {
  1011. addResult(pairs) {
  1012. const result = new ResultRow(pairs);
  1013. eResultList.insertBefore(result.element, eResultList.firstChild);
  1014. return result;
  1015. },
  1016. };
  1017.  
  1018. let data = {};
  1019. function toString() {
  1020. return JSON.stringify(data);
  1021. }
  1022. function save() {
  1023. localStorage.setItem("AtCoderEasyTest", toString());
  1024. }
  1025. function load() {
  1026. if ("AtCoderEasyTest" in localStorage) {
  1027. data = JSON.parse(localStorage.getItem("AtCoderEasyTest"));
  1028. }
  1029. }
  1030. load();
  1031. /** プロパティ名は camelCase にすること */
  1032. const config = {
  1033. getString(key, defaultValue = "") {
  1034. if (!(key in data))
  1035. config.setString(key, defaultValue);
  1036. return data[key];
  1037. },
  1038. setString(key, value) {
  1039. data[key] = value;
  1040. save();
  1041. },
  1042. has(key) {
  1043. return key in data;
  1044. },
  1045. get(key, defaultValue = null) {
  1046. if (!(key in data))
  1047. config.set(key, defaultValue);
  1048. return JSON.parse(data[key]);
  1049. },
  1050. set(key, value) {
  1051. config.setString(key, JSON.stringify(value));
  1052. },
  1053. save,
  1054. load,
  1055. toString,
  1056. };
  1057.  
  1058. var hPage = "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n <title>AtCoder Easy Test</title>\n <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css\" rel=\"stylesheet\">\n </head>\n <body>\n <div class=\"container\">\n <div class=\"panel panel-default\">\n <div class=\"panel-heading\">Settings</div>\n <div class=\"panel-body\">\n <form>\n <div class=\"checkbox\">\n <label><input id=\"use-keyboard-shortcut\" type=\"checkbox\">Use Keyboard Shortcuts</label>\n </div>\n </form>\n </div>\n </div>\n </div>\n <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js\"></script>\n <script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js\"></script>\n </body>\n</html>";
  1059.  
  1060. function bindCheckbox(key, defaultValue, element) {
  1061. element.checked = config.get(key, defaultValue);
  1062. element.addEventListener("change", () => {
  1063. config.set(key, element.checked);
  1064. });
  1065. }
  1066. const settings = {
  1067. /** 設定ページを開く
  1068. * クリックなどのイベント時にしか正しく実行できない
  1069. */
  1070. open() {
  1071. const win = window.open("about:blank");
  1072. const doc = win.document;
  1073. doc.open();
  1074. doc.write(hPage);
  1075. doc.close();
  1076. bindCheckbox("useKeyboardShortcut", true, doc.getElementById("use-keyboard-shortcut"));
  1077. }
  1078. };
  1079.  
  1080. var hTabTemplate = "<div class=\"atcoder-easy-test-result container\">\n <div class=\"row\">\n <div class=\"atcoder-easy-test-result-col-input col-xs-12\" data-if-expected-output=\"col-sm-6 col-sm-push-6\">\n <div class=\"form-group\">\n <label class=\"control-label col-xs-12\">\n Standard Input\n <div class=\"col-xs-12\">\n <textarea class=\"atcoder-easy-test-result-input form-control\" rows=\"3\" readonly=\"readonly\"></textarea>\n </div>\n </label>\n </div>\n </div>\n <div class=\"atcoder-easy-test-result-col-expected-output col-xs-12 col-sm-6 hidden\" data-if-expected-output=\"!hidden col-sm-pull-6\">\n <div class=\"form-group\">\n <label class=\"control-label col-xs-12\">\n Expected Output\n <div class=\"col-xs-12\">\n <textarea class=\"atcoder-easy-test-result-expected-output form-control\" rows=\"3\" readonly=\"readonly\"></textarea>\n </div>\n </label>\n </div>\n </div>\n </div>\n <div class=\"row\"><div class=\"col-sm-6 col-sm-offset-3\">\n <div class=\"panel panel-default\">\n <table class=\"table table-condensed\">\n <tbody>\n <tr>\n <th class=\"text-center\">Exit Code</th>\n <th class=\"text-center\">Exec Time</th>\n <th class=\"text-center\">Memory</th>\n </tr>\n <tr>\n <td class=\"atcoder-easy-test-result-exit-code text-center\"></td>\n <td class=\"atcoder-easy-test-result-exec-time text-center\"></td>\n <td class=\"atcoder-easy-test-result-memory text-center\"></td>\n </tr>\n </tbody>\n </table>\n </div>\n </div></div>\n <div class=\"row\">\n <div class=\"atcoder-easy-test-result-col-output col-xs-12\" data-if-error=\"col-md-6\">\n <div class=\"form-group\">\n <label class=\"control-label col-xs-12\">\n Standard Output\n <div class=\"col-xs-12\">\n <textarea class=\"atcoder-easy-test-result-output form-control\" rows=\"5\" readonly=\"readonly\"></textarea>\n </div>\n </label>\n </div>\n </div>\n <div class=\"atcoder-easy-test-result-col-error col-xs-12 col-md-6 hidden\" data-if-error=\"!hidden\">\n <div class=\"form-group\">\n <label class=\"control-label col-xs-12\">\n Standard Error\n <div class=\"col-xs-12\">\n <textarea class=\"atcoder-easy-test-result-error form-control\" rows=\"5\" readonly=\"readonly\"></textarea>\n </div>\n </label>\n </div>\n </div>\n </div>\n</div>";
  1081.  
  1082. function setClassFromData(element, name) {
  1083. const classes = element.dataset[name].split(/\s+/);
  1084. for (let className of classes) {
  1085. let flag = true;
  1086. if (className[0] == "!") {
  1087. className = className.slice(1);
  1088. flag = false;
  1089. }
  1090. element.classList.toggle(className, flag);
  1091. }
  1092. }
  1093. class ResultTabContent {
  1094. _title;
  1095. _uid;
  1096. _element;
  1097. _result;
  1098. constructor() {
  1099. this._uid = Date.now().toString(16);
  1100. this._result = null;
  1101. this._element = html2element(hTabTemplate);
  1102. this._element.id = `atcoder-easy-test-result-${this._uid}`;
  1103. }
  1104. set result(result) {
  1105. this._result = result;
  1106. if (result.status == "AC") {
  1107. this.outputStyle.backgroundColor = "#dff0d8";
  1108. }
  1109. else if (result.status != "OK") {
  1110. this.outputStyle.backgroundColor = "#fcf8e3";
  1111. }
  1112. this.input = result.input;
  1113. if ("expectedOutput" in result)
  1114. this.expectedOutput = result.expectedOutput;
  1115. this.exitCode = result.exitCode;
  1116. if ("execTime" in result)
  1117. this.execTime = `${result.execTime} ms`;
  1118. if ("memory" in result)
  1119. this.memory = `${result.memory} KB`;
  1120. if ("output" in result)
  1121. this.output = result.output;
  1122. if (result.error)
  1123. this.error = result.error;
  1124. }
  1125. get result() {
  1126. return this._result;
  1127. }
  1128. get uid() {
  1129. return this._uid;
  1130. }
  1131. get element() {
  1132. return this._element;
  1133. }
  1134. set title(title) {
  1135. this._title = title;
  1136. }
  1137. get title() {
  1138. return this._title;
  1139. }
  1140. set input(input) {
  1141. this._get("input").value = input;
  1142. }
  1143. get inputStyle() {
  1144. return this._get("input").style;
  1145. }
  1146. set expectedOutput(output) {
  1147. this._get("expected-output").value = output;
  1148. setClassFromData(this._get("col-input"), "ifExpectedOutput");
  1149. setClassFromData(this._get("col-expected-output"), "ifExpectedOutput");
  1150. }
  1151. get expectedOutputStyle() {
  1152. return this._get("expected-output").style;
  1153. }
  1154. set output(output) {
  1155. this._get("output").value = output;
  1156. }
  1157. get outputStyle() {
  1158. return this._get("output").style;
  1159. }
  1160. set error(error) {
  1161. this._get("error").value = error;
  1162. setClassFromData(this._get("col-output"), "ifError");
  1163. setClassFromData(this._get("col-error"), "ifError");
  1164. }
  1165. set exitCode(code) {
  1166. const element = this._get("exit-code");
  1167. element.textContent = code;
  1168. const isSuccess = code == "0";
  1169. element.classList.toggle("bg-success", isSuccess);
  1170. element.classList.toggle("bg-danger", !isSuccess);
  1171. }
  1172. set execTime(time) {
  1173. this._get("exec-time").textContent = time;
  1174. }
  1175. set memory(memory) {
  1176. this._get("memory").textContent = memory;
  1177. }
  1178. _get(name) {
  1179. return this._element.querySelector(`.atcoder-easy-test-result-${name}`);
  1180. }
  1181. }
  1182.  
  1183. var hRoot = "<form id=\"atcoder-easy-test-container\" class=\"form-horizontal\">\n <div class=\"row\">\n <div class=\"col-xs-12 col-lg-8\">\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\">Test Environment</label>\n <div class=\"col-sm-10\">\n <select class=\"form-control\" id=\"atcoder-easy-test-language\"></select>\n </div>\n </div>\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\" for=\"atcoder-easy-test-input\">Standard Input</label>\n <div class=\"col-sm-10\">\n <textarea id=\"atcoder-easy-test-input\" name=\"input\" class=\"form-control\" rows=\"3\"></textarea>\n </div>\n </div>\n </div>\n <div class=\"col-xs-12 col-lg-4\">\n <details close>\n <summary>Expected Output</summary>\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\" for=\"atcoder-easy-test-allowable-error-check\">Allowable Error</label>\n <div class=\"col-sm-10\">\n <div class=\"input-group\">\n <span class=\"input-group-addon\">\n <input id=\"atcoder-easy-test-allowable-error-check\" type=\"checkbox\" checked=\"checked\">\n </span>\n <input id=\"atcoder-easy-test-allowable-error\" type=\"text\" class=\"form-control\" value=\"1e-6\">\n </div>\n </div>\n </div>\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\" for=\"atcoder-easy-test-output\">Expected Output</label>\n <div class=\"col-sm-10\">\n <textarea id=\"atcoder-easy-test-output\" name=\"output\" class=\"form-control\" rows=\"3\"></textarea>\n </div>\n </div>\n </details>\n </div>\n <div class=\"col-xs-12 col-md-6\">\n <div class=\"col-xs-11 col-xs-offset=1\">\n <div class=\"form-group\">\n <a id=\"atcoder-easy-test-run\" class=\"btn btn-primary\">Run</a>\n </div>\n </div>\n </div>\n <div class=\"col-xs-12 col-md-6\">\n <div class=\"col-xs-11 col-xs-offset=1\">\n <div class=\"form-group text-right\">\n <small>AtCoder Easy Test v<span id=\"atcoder-easy-test-version\"></span></small>\n <a id=\"atcoder-easy-test-setting\" class=\"btn btn-xs btn-default\">Setting</a>\n </div>\n </div>\n </div>\n </div>\n <style>\n #atcoder-easy-test-language {\n border: none;\n background: transparent;\n font: inherit;\n color: #fff;\n }\n #atcoder-easy-test-language option {\n border: none;\n color: #333;\n font: inherit;\n }\n </style>\n</form>";
  1184.  
  1185. var hStyle = "<style>\n.atcoder-easy-test-result textarea {\n font-family: monospace;\n font-weight: normal;\n}\n</style>";
  1186.  
  1187. var hRunButton = "<a class=\"btn btn-primary btn-sm\" style=\"vertical-align: top; margin-left: 0.5em\">Run</a>";
  1188.  
  1189. var hTestAndSubmit = "<a id=\"atcoder-easy-test-btn-test-and-submit\" class=\"btn btn-info btn\" style=\"margin-left: 1rem\" title=\"Ctrl+Enter\" data-toggle=\"tooltip\">Test &amp; Submit</a>";
  1190.  
  1191. var hTestAllSamples = "<a id=\"atcoder-easy-test-btn-test-all\" class=\"btn btn-default btn-sm\" style=\"margin-left: 1rem\" title=\"Alt+Enter\" data-toggle=\"tooltip\">Test All Samples</a>";
  1192.  
  1193. const doc = unsafeWindow.document;
  1194. // external interfaces
  1195. unsafeWindow.bottomMenu = menuController;
  1196. unsafeWindow.codeRunner = codeRunner;
  1197. doc.head.appendChild(html2element(hStyle));
  1198. // interface
  1199. const atCoderEasyTest = {
  1200. config,
  1201. codeSaver,
  1202. enableButtons() {
  1203. events.trig("enable");
  1204. },
  1205. disableButtons() {
  1206. events.trig("disable");
  1207. },
  1208. runCount: 0,
  1209. runTest(title, language, sourceCode, input, output = null, options = { trim: true, split: true, }) {
  1210. this.disableButtons();
  1211. const content = new ResultTabContent();
  1212. const tab = menuController.addTab("easy-test-result-" + content.uid, `#${++this.runCount} ${title}`, content.element, { active: true, closeButton: true });
  1213. const pResult = codeRunner.run(language, sourceCode, input, output, options);
  1214. pResult.then(result => {
  1215. content.result = result;
  1216. if (result.status == "AC") {
  1217. tab.color = "#dff0d8";
  1218. }
  1219. else if (result.status != "OK") {
  1220. tab.color = "#fcf8e3";
  1221. }
  1222. }).finally(() => {
  1223. this.enableButtons();
  1224. });
  1225. return [pResult, tab];
  1226. }
  1227. };
  1228. unsafeWindow.atCoderEasyTest = atCoderEasyTest;
  1229. // place "Easy Test" tab
  1230. {
  1231. // declare const hRoot: string;
  1232. const root = html2element(hRoot);
  1233. const E = (id) => root.querySelector(`#atcoder-easy-test-${id}`);
  1234. const eLanguage = E("language");
  1235. const eInput = E("input");
  1236. const eAllowableErrorCheck = E("allowable-error-check");
  1237. const eAllowableError = E("allowable-error");
  1238. const eOutput = E("output");
  1239. const eRun = E("run");
  1240. const eSetting = E("setting");
  1241. E("version").textContent = "2.3.1";
  1242. events.on("enable", () => {
  1243. eRun.classList.remove("disabled");
  1244. });
  1245. events.on("disable", () => {
  1246. eRun.classList.remove("enabled");
  1247. });
  1248. eSetting.addEventListener("click", () => {
  1249. settings.open();
  1250. });
  1251. // 言語選択関係
  1252. {
  1253. eLanguage.addEventListener("change", () => {
  1254. const langSelection = config.get("langSelection", {});
  1255. langSelection[site.language.value] = eLanguage.value;
  1256. config.set("langSelection", langSelection);
  1257. });
  1258. async function setLanguage() {
  1259. const languageId = site.language.value;
  1260. while (eLanguage.firstChild)
  1261. eLanguage.removeChild(eLanguage.firstChild);
  1262. try {
  1263. const langs = await codeRunner.getEnvironment(languageId);
  1264. console.log(`language: ${langs[1]} (${langs[0]})`);
  1265. // add <option>
  1266. for (const [languageId, label] of langs) {
  1267. const option = document.createElement("option");
  1268. option.value = languageId;
  1269. option.textContent = label;
  1270. eLanguage.appendChild(option);
  1271. }
  1272. // load
  1273. const langSelection = config.get("langSelection", {});
  1274. if (languageId in langSelection) {
  1275. const prev = langSelection[languageId];
  1276. const [lang, _] = langs.find(([lang, label]) => lang == prev);
  1277. if (lang)
  1278. eLanguage.value = lang;
  1279. }
  1280. events.trig("enable");
  1281. }
  1282. catch (error) {
  1283. console.log(`language: ? (${languageId})`);
  1284. console.error(error);
  1285. const option = document.createElement("option");
  1286. option.className = "fg-danger";
  1287. option.textContent = error;
  1288. eLanguage.appendChild(option);
  1289. events.trig("disable");
  1290. }
  1291. }
  1292. site.language.addListener(() => setLanguage());
  1293. eAllowableError.disabled = !eAllowableErrorCheck.checked;
  1294. eAllowableErrorCheck.addEventListener("change", event => {
  1295. eAllowableError.disabled = !eAllowableErrorCheck.checked;
  1296. });
  1297. }
  1298. // テスト実行
  1299. function runTest(title, input, output = null) {
  1300. const options = { trim: true, split: true, };
  1301. if (eAllowableErrorCheck.checked) {
  1302. options.allowableError = parseFloat(eAllowableError.value);
  1303. }
  1304. return atCoderEasyTest.runTest(title, eLanguage.value, site.sourceCode, input, output, options);
  1305. }
  1306. function runAllCases(testcases) {
  1307. const pairs = testcases.map(testcase => runTest(testcase.title, testcase.input, testcase.output));
  1308. resultList.addResult(pairs);
  1309. return Promise.all(pairs.map(([pResult, _]) => pResult.then(result => {
  1310. if (result.status == "AC")
  1311. return Promise.resolve(result);
  1312. else
  1313. return Promise.reject(result);
  1314. })));
  1315. }
  1316. eRun.addEventListener("click", _ => {
  1317. const title = "Run";
  1318. const input = eInput.value;
  1319. const output = eOutput.value;
  1320. runTest(title, input, output || null);
  1321. });
  1322. menuController.addTab("easy-test", "Easy Test", root);
  1323. // place "Run" button on each sample
  1324. for (const testCase of site.testCases) {
  1325. const eRunButton = html2element(hRunButton);
  1326. eRunButton.addEventListener("click", async () => {
  1327. const [pResult, tab] = runTest(testCase.title, testCase.input, testCase.output);
  1328. await pResult;
  1329. tab.show();
  1330. });
  1331. testCase.anchor.insertAdjacentElement("afterend", eRunButton);
  1332. events.on("disable", () => {
  1333. eRunButton.classList.add("disabled");
  1334. });
  1335. events.on("enable", () => {
  1336. eRunButton.classList.remove("disabled");
  1337. });
  1338. }
  1339. // place "Test & Submit" button
  1340. {
  1341. const button = html2element(hTestAndSubmit);
  1342. site.testButtonContainer.appendChild(button);
  1343. const testAndSubmit = async () => {
  1344. await runAllCases(site.testCases);
  1345. site.submit();
  1346. };
  1347. button.addEventListener("click", testAndSubmit);
  1348. events.on("testAndSubmit", testAndSubmit);
  1349. }
  1350. // place "Test All Samples" button
  1351. {
  1352. const button = html2element(hTestAllSamples);
  1353. site.testButtonContainer.appendChild(button);
  1354. const testAllSamples = () => runAllCases(site.testCases);
  1355. button.addEventListener("click", testAllSamples);
  1356. events.on("testAllSamples", testAllSamples);
  1357. }
  1358. }
  1359. // place "Restore Last Play" button
  1360. try {
  1361. const restoreButton = doc.createElement("a");
  1362. restoreButton.className = "btn btn-danger btn-sm";
  1363. restoreButton.textContent = "Restore Last Play";
  1364. restoreButton.addEventListener("click", async () => {
  1365. try {
  1366. const lastCode = await codeSaver.restore();
  1367. if (site.sourceCode.length == 0 || confirm("Your current code will be replaced. Are you sure?")) {
  1368. site.sourceCode = lastCode;
  1369. }
  1370. }
  1371. catch (reason) {
  1372. alert(reason);
  1373. }
  1374. });
  1375. site.sideButtonContainer.appendChild(restoreButton);
  1376. }
  1377. catch (e) {
  1378. console.error(e);
  1379. }
  1380. // キーボードショートカット
  1381. unsafeWindow.addEventListener("keydown", (event) => {
  1382. if (config.get("useKeyboardShortcut", true)) {
  1383. if (event.key == "Enter" && event.ctrlKey) {
  1384. events.trig("testAndSubmit");
  1385. }
  1386. else if (event.key == "Enter" && event.altKey) {
  1387. events.trig("testAllSamples");
  1388. }
  1389. else if (event.key == "Escape" && event.altKey) {
  1390. menuController.toggle();
  1391. }
  1392. }
  1393. });
  1394. })();