Greasy Fork is available in English.

MoonBit ❤️ LeetCode

add support of moonbit language to leetcode

ของเมื่อวันที่ 03-05-2025 ดู เวอร์ชันล่าสุด

  1. // ==UserScript==
  2. // @name MoonBit ❤️ LeetCode
  3. // @namespace a23187.cn
  4. // @version 1.0.1
  5. // @description add support of moonbit language to leetcode
  6. // @author A23187
  7. // @homepage https://github.com/A-23187/moonbit-leetcode
  8. // @match https://leetcode.cn/problems/*
  9. // @match https://leetcode.com/problems/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=leetcode.cn
  11. // @grant none
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (async function() {
  16. 'use strict';
  17. async function waitUntil(cond) {
  18. await new Promise((resolve) => {
  19. const id = setInterval(() => {
  20. if (cond()) {
  21. clearInterval(id);
  22. resolve();
  23. }
  24. }, 1000);
  25. });
  26. }
  27. async function createObjectUrlFromCORSUrl(url) {
  28. return await fetch(url)
  29. .then((resp) => resp.text())
  30. .then((cnt) => URL.createObjectURL(new Blob([cnt], { type: 'application/javascript' })));
  31. }
  32. async function createWorkerFromCORSUrl(url) {
  33. return new Worker(await createObjectUrlFromCORSUrl(url));
  34. }
  35. // wait until the `globalThis.monaco` is presented
  36. await waitUntil(() => globalThis.monaco !== undefined);
  37. // init `moonpad`, `moon`
  38. const baseUrl = 'https://cdn.jsdelivr.net/gh/A-23187/moonbit-leetcode/moonpad-monaco';
  39. const moonpad = await import(`${baseUrl}/moonpad-monaco.js`);
  40. const lspWorker = await createWorkerFromCORSUrl(`${baseUrl}/lsp-server.js`);
  41. const mooncWorkerUrl = await createObjectUrlFromCORSUrl(`${baseUrl}/moonc-worker.js`);
  42. function initMoon() {
  43. globalThis.moon = globalThis.moon ?? moonpad.init({
  44. onigWasmUrl: `${baseUrl}/onig.wasm`,
  45. lspWorker,
  46. mooncWorkerFactory: () => new Worker(mooncWorkerUrl),
  47. codeLensFilter: () => false,
  48. });
  49. }
  50. globalThis.moon = null;
  51. globalThis.moonpad = moonpad;
  52. // handle language switching
  53. const toLanguageId = (function() {
  54. const languageMap = new Map([['C++', 'cpp'], ['C#', 'csharp'], ['Go', 'golang']]);
  55. return (languageName) => (
  56. languageMap.has(languageName) ? languageMap.get(languageName) : languageName.toLowerCase());
  57. })();
  58. function getCurrentLanguageId() {
  59. return globalThis.monaco.editor.getEditors()[0].getModel().getLanguageId();
  60. }
  61. function getQuestion() {
  62. const nextData = JSON.parse(document.getElementById('__NEXT_DATA__').innerText);
  63. const queries = nextData.props.pageProps.dehydratedState.queries;
  64. for (const q of queries) {
  65. for (const k of q.queryKey) {
  66. if (k === 'questionDetail') {
  67. return q.state.data.question;
  68. }
  69. }
  70. }
  71. return null;
  72. }
  73. function getQuestionMetaData() {
  74. return JSON.parse(getQuestion().metaData);
  75. }
  76. const parseType = (function() {
  77. const typeMap = new Map([
  78. ['void', 'Unit'], ['boolean', 'Bool'], ['integer', 'Int'], ['long', 'Int64'],
  79. ['float', 'Float'], ['double', 'Double'], ['char', 'Char'], ['string', 'String'],
  80. ]);
  81. const dfs = (type, begin, end) => {
  82. if (begin >= end) {
  83. return '';
  84. }
  85. if (type.endsWith('[]', end)) {
  86. return `Array[${dfs(type, begin, end - 2)}]`;
  87. }
  88. if (type.startsWith('list<', begin)) {
  89. return `Array[${dfs(type, begin + 5, end - 1)}]`;
  90. }
  91. const t = type.substring(begin, end);
  92. return typeMap.get(t) ?? t;
  93. };
  94. return (type) => dfs(type, 0, type.length);
  95. })();
  96. function generateMoonCodeTemplate() {
  97. const { name, params, return: { type: returnType } } = getQuestionMetaData();
  98. return `pub fn ${name}(${params.map((p) => `${p.name}: ${parseType(p.type)}`).join(', ')}) -> ${parseType(returnType)} {\n}\n`;
  99. }
  100. const switchLanguage = (function() {
  101. const monaco = globalThis.monaco;
  102. let moonModel = null;
  103. let nonMoonModel = null;
  104. return (languageName) => {
  105. const currLanguageId = getCurrentLanguageId();
  106. const languageId = toLanguageId(languageName);
  107. if (currLanguageId === languageId) {
  108. return;
  109. }
  110. if (languageId === 'moonbit') {
  111. initMoon();
  112. moonModel = moonModel ?? monaco.editor.createModel(generateMoonCodeTemplate(), languageId);
  113. monaco.editor.getEditors()[0].setModel(moonModel);
  114. } else if (currLanguageId === 'moonbit') {
  115. nonMoonModel = nonMoonModel ?? monaco.editor.createModel('', languageId);
  116. monaco.editor.getEditors()[0].setModel(nonMoonModel);
  117. }
  118. };
  119. })();
  120. const mutationObserver = new MutationObserver((mutations) => {
  121. for (const m of mutations) {
  122. if (m.type !== 'childList' || !m.addedNodes?.item(0)?.innerText?.startsWith('C++\nJava\nPython\nPython3')) {
  123. continue;
  124. }
  125. const switchLanguageBtn = document.querySelector('#editor > div:nth-child(1) button:nth-child(1) > button');
  126. const languageSelectionDiv = m.addedNodes[0].querySelector('div > div > div');
  127. const lastColDiv = languageSelectionDiv.lastElementChild;
  128. const moonDiv = lastColDiv.lastElementChild.cloneNode(true);
  129. moonDiv.querySelector('div > div > div').innerText = 'MoonBit';
  130. lastColDiv.appendChild(moonDiv);
  131. for (const colDiv of languageSelectionDiv.children) {
  132. for (const itemDiv of colDiv.children) {
  133. const svg = moonDiv.querySelector('div > div > svg');
  134. if (toLanguageId(itemDiv.innerText) === getCurrentLanguageId()) {
  135. svg.classList.add('visible');
  136. svg.classList.remove('invisible');
  137. } else {
  138. svg.classList.add('invisible');
  139. svg.classList.remove('visible');
  140. }
  141. itemDiv.onclick = () => {
  142. switchLanguage(itemDiv.innerText);
  143. switchLanguageBtn.firstChild.data = itemDiv.innerText;
  144. };
  145. }
  146. }
  147. break;
  148. }
  149. });
  150. mutationObserver.observe(document.body, { childList: true });
  151. // compile
  152. async function compile(commentSource = false) {
  153. const editor = globalThis.monaco.editor.getEditors()[0];
  154. const { name } = getQuestionMetaData();
  155. const result = await globalThis.moon.compile({
  156. libInputs: [[`${name}.mbt`, editor.getValue()]],
  157. isMain: false,
  158. exportedFunctions: [name],
  159. });
  160. if (result.kind === 'success') {
  161. return `${commentSource && editor.getValue().trim().replace(/^/gm, '// ') || ''}\n${
  162. new TextDecoder().decode(result.js)
  163. .replace(/export\s*\{\s*([^\s]+)\s+as\s+([^\s]+)\s*\}/g, 'const $2 = $1;')}`;
  164. } else if (result.kind === 'error') {
  165. throw new Error(result.diagnostics.map((d) => `${name}.mbt:${d.loc.start.line} ${d.message}\n ${
  166. editor.getModel().getValueInRange({
  167. startLineNumber: d.loc.start.line,
  168. startColumn: d.loc.start.col,
  169. endLineeNumber: d.loc.end.line,
  170. endColumn: d.loc.end.col,
  171. })}`).join('\n\n'));
  172. }
  173. return null;
  174. }
  175. // run and submit
  176. globalThis._fetch = globalThis.fetch;
  177. globalThis.fetch = async (resource, options) => {
  178. // pre hook
  179. if ((resource === `${document.location.pathname}interpret_solution/` ||
  180. resource === `${document.location.pathname}submit/`) && getCurrentLanguageId() === 'moonbit') {
  181. const body = JSON.parse(options.body);
  182. body.lang = 'javascript';
  183. body.typed_code = await compile(true)
  184. .catch((e) => `throw'MOON_ERR_BEGIN\\n'+${JSON.stringify(e.message)}+'\\nMOON_ERR_END'`);
  185. options.body = JSON.stringify(body);
  186. }
  187. const r = globalThis._fetch(resource, options);
  188. // post hook
  189. if (resource.match(/^\/submissions\/detail\/[^/]+\/check\/$/)) {
  190. const checkResult = await r.then((resp) => resp.clone().json());
  191. const { full_runtime_error: fullRuntimeError = '' } = checkResult;
  192. const [_, moonError = null] = fullRuntimeError.match(/MOON_ERR_BEGIN\n([\s\S]+)\nMOON_ERR_END/) ?? [];
  193. if (_ && moonError) {
  194. checkResult.full_runtime_error = moonError;
  195. return Response.json(checkResult);
  196. }
  197. return r;
  198. }
  199. return r;
  200. };
  201. })();