From 66540b868b8198775a100bde953bab70488b13d0 Mon Sep 17 00:00:00 2001 From: "pullfrog[bot]" <226033991+pullfrog[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 09:18:50 +0000 Subject: [PATCH 1/5] feat: add V language support --- .../language-support/references/languages.md | 1 + README.md | 2 +- docs/docs/languages/v-wasm.mdx | 109 ++++++ docs/src/components/LanguageSliders.tsx | 1 + docs/src/components/TemplateList.tsx | 1 + functions/vendors/templates.js | 1 + scripts/build.js | 1 + server/php/inc/starter-templates.json | 1 + src/livecodes/UI/command-menu-actions.ts | 1 + src/livecodes/assets/templates/v.svg | 4 + src/livecodes/html/language-info.html | 31 ++ .../locales/ar/language-info.lokalise.json | 9 + .../i18n/locales/ar/language-info.ts | 5 + .../i18n/locales/ar/translation.lokalise.json | 3 + src/livecodes/i18n/locales/ar/translation.ts | 1 + .../locales/bn/language-info.lokalise.json | 9 + .../i18n/locales/bn/language-info.ts | 5 + .../i18n/locales/bn/translation.lokalise.json | 3 + src/livecodes/i18n/locales/bn/translation.ts | 1 + .../locales/de/language-info.lokalise.json | 9 + .../i18n/locales/de/language-info.ts | 5 + .../i18n/locales/de/translation.lokalise.json | 3 + src/livecodes/i18n/locales/de/translation.ts | 1 + .../locales/en/language-info.lokalise.json | 12 + .../i18n/locales/en/language-info.ts | 5 + .../i18n/locales/en/translation.lokalise.json | 4 + src/livecodes/i18n/locales/en/translation.ts | 1 + .../locales/es/language-info.lokalise.json | 9 + .../i18n/locales/es/language-info.ts | 5 + .../i18n/locales/es/translation.lokalise.json | 3 + src/livecodes/i18n/locales/es/translation.ts | 1 + .../locales/fa/language-info.lokalise.json | 9 + .../i18n/locales/fa/language-info.ts | 5 + .../i18n/locales/fa/translation.lokalise.json | 3 + src/livecodes/i18n/locales/fa/translation.ts | 1 + .../locales/fr/language-info.lokalise.json | 9 + .../i18n/locales/fr/language-info.ts | 5 + .../i18n/locales/fr/translation.lokalise.json | 3 + src/livecodes/i18n/locales/fr/translation.ts | 1 + .../locales/hi/language-info.lokalise.json | 9 + .../i18n/locales/hi/language-info.ts | 5 + .../i18n/locales/hi/translation.lokalise.json | 3 + src/livecodes/i18n/locales/hi/translation.ts | 1 + .../locales/hu/language-info.lokalise.json | 9 + .../i18n/locales/hu/language-info.ts | 5 + .../i18n/locales/hu/translation.lokalise.json | 3 + src/livecodes/i18n/locales/hu/translation.ts | 1 + .../locales/id/language-info.lokalise.json | 9 + .../i18n/locales/id/language-info.ts | 5 + .../i18n/locales/id/translation.lokalise.json | 3 + src/livecodes/i18n/locales/id/translation.ts | 1 + .../locales/it/language-info.lokalise.json | 9 + .../i18n/locales/it/language-info.ts | 5 + .../i18n/locales/it/translation.lokalise.json | 3 + src/livecodes/i18n/locales/it/translation.ts | 1 + .../locales/ja/language-info.lokalise.json | 9 + .../i18n/locales/ja/language-info.ts | 5 + .../i18n/locales/ja/translation.lokalise.json | 3 + src/livecodes/i18n/locales/ja/translation.ts | 1 + .../locales/nl/language-info.lokalise.json | 9 + .../i18n/locales/nl/language-info.ts | 5 + .../i18n/locales/nl/translation.lokalise.json | 3 + src/livecodes/i18n/locales/nl/translation.ts | 1 + .../locales/pt/language-info.lokalise.json | 9 + .../i18n/locales/pt/language-info.ts | 5 + .../i18n/locales/pt/translation.lokalise.json | 3 + src/livecodes/i18n/locales/pt/translation.ts | 1 + .../locales/ru/language-info.lokalise.json | 9 + .../i18n/locales/ru/language-info.ts | 5 + .../i18n/locales/ru/translation.lokalise.json | 3 + src/livecodes/i18n/locales/ru/translation.ts | 1 + .../locales/tr/language-info.lokalise.json | 9 + .../i18n/locales/tr/language-info.ts | 5 + .../i18n/locales/tr/translation.lokalise.json | 3 + src/livecodes/i18n/locales/tr/translation.ts | 1 + .../locales/ur/language-info.lokalise.json | 9 + .../i18n/locales/ur/language-info.ts | 5 + .../i18n/locales/ur/translation.lokalise.json | 3 + src/livecodes/i18n/locales/ur/translation.ts | 1 + .../locales/zh-CN/language-info.lokalise.json | 9 + .../i18n/locales/zh-CN/language-info.ts | 5 + .../locales/zh-CN/translation.lokalise.json | 3 + .../i18n/locales/zh-CN/translation.ts | 1 + src/livecodes/languages/languages.ts | 2 + .../v-wasm/__tests__/lang-v-wasm.spec.ts | 32 ++ src/livecodes/languages/v-wasm/index.ts | 1 + .../languages/v-wasm/lang-v-wasm-script.ts | 329 ++++++++++++++++++ src/livecodes/languages/v-wasm/lang-v-wasm.ts | 29 ++ src/livecodes/models.ts | 1 + src/livecodes/templates/starter/index.ts | 2 + .../templates/starter/v-wasm-starter.ts | 89 +++++ src/livecodes/vendors.ts | 6 + src/sdk/models.ts | 7 + storybook/_stories/EmbedOptions/template.ts | 1 + .../stories/EmbedOptions/template.stories.ts | 1 + .../stories/EmbedOptions/template.stories.ts | 1 + .../stories/EmbedOptions/template.stories.ts | 1 + .../stories/EmbedOptions/template.stories.ts | 1 + .../stories/EmbedOptions/template.stories.ts | 1 + .../stories/EmbedOptions/template.stories.ts | 1 + vendor-licenses.md | 4 + 101 files changed, 989 insertions(+), 1 deletion(-) create mode 100644 docs/docs/languages/v-wasm.mdx create mode 100644 src/livecodes/assets/templates/v.svg create mode 100644 src/livecodes/languages/v-wasm/__tests__/lang-v-wasm.spec.ts create mode 100644 src/livecodes/languages/v-wasm/index.ts create mode 100644 src/livecodes/languages/v-wasm/lang-v-wasm-script.ts create mode 100644 src/livecodes/languages/v-wasm/lang-v-wasm.ts create mode 100644 src/livecodes/templates/starter/v-wasm-starter.ts diff --git a/.agents/skills/livecodes/language-support/references/languages.md b/.agents/skills/livecodes/language-support/references/languages.md index 66a48dcb2f..b603139094 100644 --- a/.agents/skills/livecodes/language-support/references/languages.md +++ b/.agents/skills/livecodes/language-support/references/languages.md @@ -148,6 +148,7 @@ Enabled via `processors` config: | Rust (Wasm) | `rust-wasm`, `rs-wasm`, `wasm.rs` | Rust WASM | | Swift (Wasm) | `swift-wasm`, `swift`, `wasm.swift` | Swift WASM | | Zig (Wasm) | `zig-wasm`, `zig` | Zig WASM | +| V (Wasm) | `v-wasm`, `v`, `vlang`, `wasm.v`, `vsh` | V compiler in WASM | | Tcl | `tcl` | Tcl interpreter | | WebAssembly | `wat`, `wast`, `wasm`, `webassembly` | WAT format | | Assembly (Wasm) | `assembly-wasm`, `assembly`, `asm`, `asm-wasm`, `x86`, `x86-64`, `nasm`, `wasm.asm` | Keystone + Unicorn (WASM), x86-64 assembly | diff --git a/README.md b/README.md index 8cc8a1d294..08af338fb2 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A [feature-rich](https://livecodes.io/docs/features/), open-source, **client-sid [![LiveCodes: npm version](https://img.shields.io/npm/v/livecodes)](https://www.npmjs.com/package/livecodes) [![LiveCodes: npm downloads](https://img.shields.io/npm/dm/livecodes)](https://www.npmjs.com/package/livecodes) [![LiveCodes: jsdelivr downloads](https://data.jsdelivr.com/v1/package/npm/livecodes/badge?style=rounded)](https://www.jsdelivr.com/package/npm/livecodes) -[![LiveCodes: languages](https://img.shields.io/badge/languages-115-blue)](https://livecodes.io/docs/languages/) +[![LiveCodes: languages](https://img.shields.io/badge/languages-116-blue)](https://livecodes.io/docs/languages/) [![LiveCodes: docs](https://img.shields.io/badge/Documentation-575757?logo=gitbook&logoColor=white)](https://livecodes.io/docs/) [![LiveCodes: llms.txt](https://img.shields.io/badge/llms.txt-575757?logo=googledocs&logoColor=white)](https://livecodes.io/docs/llms.txt) [![LiveCodes: llms-full.txt](https://img.shields.io/badge/llms--full.txt-575757?logo=googledocs&logoColor=white)](https://livecodes.io/docs/llms-full.txt) diff --git a/docs/docs/languages/v-wasm.mdx b/docs/docs/languages/v-wasm.mdx new file mode 100644 index 0000000000..8d519e46c0 --- /dev/null +++ b/docs/docs/languages/v-wasm.mdx @@ -0,0 +1,109 @@ +# V (Wasm) + +V is a statically typed, compiled programming language designed for building maintainable software. + +In LiveCodes, V runs in the browser using WebAssembly: the V compiler is compiled to WebAssembly and +the C it emits is compiled and linked by the same Clang toolchain the C/C++ languages use. No server is +involved. + +## Usage + +Demo: + +import LiveCodes from '../../src/components/LiveCodes.tsx'; + +export const vConfig = { + activeEditor: 'script', + script: { + language: 'v-wasm', + content: `fn main() { + println('Hello, World!') +}`, + }, + mode: 'simple', + editor: 'auto', + tools: { + status: 'full', + }, +}; + + + +### Communication with JavaScript + +The program runs in the context of the result page. A few helpers are available in the browser global +`livecodes.v` object: + +- `livecodes.v.input`: the standard input passed to the program. +- `livecodes.v.loaded`: a promise that resolves when the compiler has downloaded and the program has + run. It rejects if the environment fails to load. Other helpers should be used after it resolves. +- `livecodes.v.output`: the program's standard output. +- `livecodes.v.error`: the compiler diagnostics, if the code failed to compile. +- `livecodes.v.exitCode`: the exit code of the program. +- `livecodes.v.run`: compiles and runs the code again with new input, returning a promise that + resolves with `output`, `error` and `exitCode`. + +Example: + + + +```v +fn main() { + println('Hello, World!') +} +``` + +The program can read from the **stdin** pane, in the same way as the other console languages: + +```v +import os + +fn main() { + name := os.input('What is your name? ') + println('Hello, ' + name + '!') +} +``` + +## Language Info + +### Name + +`v-wasm` + +### Aliases / Extensions + +`v`, `vlang`, `v-wasm`, `wasm.v`, `vsh` + +### Editor + +`script` + +## Compiler + +The V compiler compiled to WebAssembly, with its generated C compiled and linked by +[Clang](https://clang.llvm.org/) compiled to WebAssembly. + +### Version + +V 0.5.2, built with Emscripten, and the `@live-codes/clang-wasm` toolchain. + +## Code Formatting + +Not currently supported. + +## Live Reload + +By default, new code changes are sent to the result page for re-evaluation without a full page reload, avoiding the need to reinitialize the V WebAssembly environment. This behavior can be disabled by adding the code comment `// __livecodes_reload__` to the V code, which forces a full page reload. + +This comment can be added in the `hiddenContent` property of the editor for embedded playgrounds. + +## Starter Template + +https://livecodes.io/?template=v-wasm + +## Links + +- [V official website](https://vlang.io/) +- [V documentation](https://github.com/vlang/v/blob/master/doc/docs.md) +- [Learn X in Y minutes, where X=V](https://learnxinyminutes.com/v/) +- [@live-codes/v-wasm](https://www.npmjs.com/package/@live-codes/v-wasm) diff --git a/docs/src/components/LanguageSliders.tsx b/docs/src/components/LanguageSliders.tsx index e87b4a218b..bf379b2d3d 100644 --- a/docs/src/components/LanguageSliders.tsx +++ b/docs/src/components/LanguageSliders.tsx @@ -93,6 +93,7 @@ export default function Sliders() { { name: 'objc-wasm', title: 'Objective-C (Wasm)' }, { name: 'objcpp-wasm', title: 'Objective-C++ (Wasm)' }, { name: 'fortran', title: 'Fortran' }, + { name: 'v-wasm', title: 'V (Wasm)' }, { name: 'rust-wasm', title: 'Rust (Wasm)' }, { name: 'swift-wasm', title: 'Swift (Wasm)' }, { name: 'haskell', title: 'Haskell' }, diff --git a/docs/src/components/TemplateList.tsx b/docs/src/components/TemplateList.tsx index 16e56b01d9..ce6c08edb3 100644 --- a/docs/src/components/TemplateList.tsx +++ b/docs/src/components/TemplateList.tsx @@ -49,6 +49,7 @@ const templates = [ { name: 'cpp', title: 'C++ Starter', thumbnail: 'cpp.svg' }, { name: 'c-wasm', title: 'C (Wasm) Starter', thumbnail: 'c.svg' }, { name: 'fortran-wasm', title: 'Fortran Starter', thumbnail: 'fortran.svg' }, + { name: 'v-wasm', title: 'V Starter', thumbnail: 'v.svg' }, { name: 'cpp-wasm', title: 'C++ (Wasm) Starter', thumbnail: 'cpp.svg' }, { name: 'objc-wasm', title: 'Objective-C (Wasm) Starter', thumbnail: 'objective-c.svg' }, { name: 'rust-wasm', title: 'Rust (Wasm) Starter', thumbnail: 'rust.svg' }, diff --git a/functions/vendors/templates.js b/functions/vendors/templates.js index 347939811e..e4067d2280 100644 --- a/functions/vendors/templates.js +++ b/functions/vendors/templates.js @@ -51,6 +51,7 @@ export const starterTemplates = { "cpp-wasm": "C++ (Wasm) Starter", "objc-wasm": "Objective-C (Wasm) Starter", "fortran-wasm": "Fortran Starter", + "v-wasm": "V Starter", "rust-wasm": "Rust (Wasm) Starter", "swift-wasm": "Swift (Wasm) Starter", "zig-wasm": "Zig (Wasm) Starter", diff --git a/scripts/build.js b/scripts/build.js index 81a0f32183..14b212de7c 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -229,6 +229,7 @@ const iifeBuild = () => 'languages/cpp/lang-cpp-script.ts', 'languages/clang-wasm/lang-clang-wasm-script.ts', 'languages/fortran-wasm/lang-fortran-wasm-script.ts', + 'languages/v-wasm/lang-v-wasm-script.ts', 'languages/go-wasm/lang-go-wasm-script.ts', 'languages/csharp-wasm/lang-csharp-wasm-script.ts', 'languages/fanak/lang-fanak-script.ts', diff --git a/server/php/inc/starter-templates.json b/server/php/inc/starter-templates.json index cfa96c36b4..194faf6cbf 100644 --- a/server/php/inc/starter-templates.json +++ b/server/php/inc/starter-templates.json @@ -51,6 +51,7 @@ "cpp-wasm": "C++ (Wasm) Starter", "objc-wasm": "Objective-C (Wasm) Starter", "fortran-wasm": "Fortran Starter", + "v-wasm": "V Starter", "rust-wasm": "Rust (Wasm) Starter", "swift-wasm": "Swift (Wasm) Starter", "zig-wasm": "Zig (Wasm) Starter", diff --git a/src/livecodes/UI/command-menu-actions.ts b/src/livecodes/UI/command-menu-actions.ts index d03bd476f2..755e6c1614 100644 --- a/src/livecodes/UI/command-menu-actions.ts +++ b/src/livecodes/UI/command-menu-actions.ts @@ -307,6 +307,7 @@ export const getCommandMenuActions = ({ 'cpp-wasm', 'objc-wasm', 'fortran-wasm', + 'v-wasm', 'rust-wasm', 'swift-wasm', 'zig-wasm', diff --git a/src/livecodes/assets/templates/v.svg b/src/livecodes/assets/templates/v.svg new file mode 100644 index 0000000000..f93a628df4 --- /dev/null +++ b/src/livecodes/assets/templates/v.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/livecodes/html/language-info.html b/src/livecodes/html/language-info.html index f9f9713781..0c073437df 100644 --- a/src/livecodes/html/language-info.html +++ b/src/livecodes/html/language-info.html @@ -2698,6 +2698,37 @@

TypeScript

+
+

V (Wasm)

+
+ V compiler running on WebAssembly. +
+ +

VB.NET (Wasm)

diff --git a/src/livecodes/i18n/locales/ar/language-info.lokalise.json b/src/livecodes/i18n/locales/ar/language-info.lokalise.json index 74d4ff58b6..432a012e54 100644 --- a/src/livecodes/i18n/locales/ar/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/ar/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "مترجم V يعمل على WebAssembly." + }, + "v-wasm.link": { + "translation": " موقع V وثائق لغة V تعلم X في Y دقيقة، حيث X = V وثائق LiveCodes تحميل قالب البداية " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "مترجم VB.NET الذي يعمل على WebAssembly، باستخدام Roslyn وبيئة تشغيل .NET WebAssembly" }, diff --git a/src/livecodes/i18n/locales/ar/language-info.ts b/src/livecodes/i18n/locales/ar/language-info.ts index 0da8da1c90..4a41e155fe 100644 --- a/src/livecodes/i18n/locales/ar/language-info.ts +++ b/src/livecodes/i18n/locales/ar/language-info.ts @@ -511,6 +511,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>الموقع الرسمي <3> <4>وثائق TypeScript <5> <6>تعلم X في Y دقائق، حيث X=TypeScript <7> <8>تحميل القالب الأولي ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'مترجم V يعمل على WebAssembly.', + link: '<1> <2>موقع V <3> <4>وثائق لغة V <5> <6>تعلم X في Y دقيقة، حيث X = V <7> <8>وثائق LiveCodes <9> <10>تحميل قالب البداية ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'مترجم VB.NET الذي يعمل على WebAssembly، باستخدام <1>Roslyn و<2>بيئة تشغيل .NET WebAssembly', link: '<1> <2>وثائق لغة Visual Basic <3> <4> تعلم X في دقائق Y، حيث X = Visual Basic <5> <6> وثائق LiveCodes <7> <8> تحميل قالب البدء ', diff --git a/src/livecodes/i18n/locales/ar/translation.lokalise.json b/src/livecodes/i18n/locales/ar/translation.lokalise.json index a42cef5d1f..97fb2ed857 100644 --- a/src/livecodes/i18n/locales/ar/translation.lokalise.json +++ b/src/livecodes/i18n/locales/ar/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "قالب TypeScript" }, + "templates.starter.v-wasm": { + "translation": "قالب V" + }, "templates.starter.vb-wasm": { "translation": "قالب VB.NET (Wasm)" }, diff --git a/src/livecodes/i18n/locales/ar/translation.ts b/src/livecodes/i18n/locales/ar/translation.ts index 62b9a1866a..028cd44c46 100644 --- a/src/livecodes/i18n/locales/ar/translation.ts +++ b/src/livecodes/i18n/locales/ar/translation.ts @@ -1059,6 +1059,7 @@ const translation: I18nTranslation = { tcl: 'قالب Tcl', teal: 'قالب Teal', typescript: 'قالب TypeScript', + 'v-wasm': 'قالب V', 'vb-wasm': 'قالب VB.NET (Wasm)', vue: 'قالب Vue', vue2: 'قالب Vue 2', diff --git a/src/livecodes/i18n/locales/bn/language-info.lokalise.json b/src/livecodes/i18n/locales/bn/language-info.lokalise.json index 2209c6b338..31e0a34e22 100644 --- a/src/livecodes/i18n/locales/bn/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/bn/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "V কম্পাইলার ওয়েবঅ্যাসেম্বলিতে চলছে।" + }, + "v-wasm.link": { + "translation": " V ওয়েবসাইট V ল্যাঙ্গুয়েজ ডকুমেন্টেশন Y মিনিটে X শিখুন, যেখানে X=V LiveCodes ডকুমেন্টেশন স্টার্টার টেমপ্লেট লোড করুন " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "WebAssembly-তে চলমান VB.NET কম্পাইলার, Roslyn এবং .NET WebAssembly রানটাইম ব্যবহার করে" }, diff --git a/src/livecodes/i18n/locales/bn/language-info.ts b/src/livecodes/i18n/locales/bn/language-info.ts index e343eb9c07..8e1303e142 100644 --- a/src/livecodes/i18n/locales/bn/language-info.ts +++ b/src/livecodes/i18n/locales/bn/language-info.ts @@ -513,6 +513,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>অফিসিয়াল ওয়েবসাইট <3> <4>TypeScript ডকুমেন্টেশন <5> <6>Y মিনিটে X শিখুন, যেখানে X=TypeScript <7> <8>স্টার্টার টেমপ্লেট লোড করুন ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'V কম্পাইলার ওয়েবঅ্যাসেম্বলিতে চলছে।', + link: '<1> <2>V ওয়েবসাইট <3> <4>V ল্যাঙ্গুয়েজ ডকুমেন্টেশন <5> <6>Y মিনিটে X শিখুন, যেখানে X=V <7> <8>LiveCodes ডকুমেন্টেশন <9> <10>স্টার্টার টেমপ্লেট লোড করুন ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'WebAssembly-তে চলমান VB.NET কম্পাইলার, <1>Roslyn এবং <2>.NET WebAssembly রানটাইম ব্যবহার করে', link: '<1> <2>Visual Basic ভাষা ডকুমেন্টেশন <3> <4>Y মিনিটে X শিখুন, যেখানে X=Visual Basic <5> <6>LiveCodes ডকুমেন্টেশন <7> <8>স্টার্টার টেমপ্লেট লোড করুন ', diff --git a/src/livecodes/i18n/locales/bn/translation.lokalise.json b/src/livecodes/i18n/locales/bn/translation.lokalise.json index 04e292fb20..06d31f9e53 100644 --- a/src/livecodes/i18n/locales/bn/translation.lokalise.json +++ b/src/livecodes/i18n/locales/bn/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "TypeScript স্টার্টার" }, + "templates.starter.v-wasm": { + "translation": "V স্টার্টার" + }, "templates.starter.vb-wasm": { "translation": "VB.NET (Wasm) স্টার্টার" }, diff --git a/src/livecodes/i18n/locales/bn/translation.ts b/src/livecodes/i18n/locales/bn/translation.ts index b903338c21..89f9b97d59 100644 --- a/src/livecodes/i18n/locales/bn/translation.ts +++ b/src/livecodes/i18n/locales/bn/translation.ts @@ -1059,6 +1059,7 @@ const translation: I18nTranslation = { tcl: 'Tcl স্টার্টার', teal: 'Teal স্টার্টার', typescript: 'TypeScript স্টার্টার', + 'v-wasm': 'V স্টার্টার', 'vb-wasm': 'VB.NET (Wasm) স্টার্টার', vue: 'Vue SFC স্টার্টার', vue2: 'Vue 2 স্টার্টার', diff --git a/src/livecodes/i18n/locales/de/language-info.lokalise.json b/src/livecodes/i18n/locales/de/language-info.lokalise.json index e76c371e75..8551c7bd10 100644 --- a/src/livecodes/i18n/locales/de/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/de/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "V-Compiler, der auf WebAssembly läuft." + }, + "v-wasm.link": { + "translation": " V-Website V-Sprachdokumentation Lerne X in Y Minuten, wobei X = V LiveCodes-Dokumentation Starter-Vorlage laden " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "VB.NET-Compiler, der auf WebAssembly läuft, unter Verwendung von Roslyn und der .NET-WebAssembly-Laufzeitumgebung" }, diff --git a/src/livecodes/i18n/locales/de/language-info.ts b/src/livecodes/i18n/locales/de/language-info.ts index a4adf884fb..d58768ba3a 100644 --- a/src/livecodes/i18n/locales/de/language-info.ts +++ b/src/livecodes/i18n/locales/de/language-info.ts @@ -515,6 +515,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>Offizielle Website <3> <4>TypeScript-Dokumentation <5> <6>Lerne X in Y Minuten, wobei X=TypeScript <7> <8>Starter-Vorlage laden ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'V-Compiler, der auf WebAssembly läuft.', + link: '<1> <2>V-Website <3> <4>V-Sprachdokumentation <5> <6>Lerne X in Y Minuten, wobei X = V <7> <8>LiveCodes-Dokumentation <9> <10>Starter-Vorlage laden ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'VB.NET-Compiler, der auf WebAssembly läuft, unter Verwendung von <1>Roslyn und der <2>.NET-WebAssembly-Laufzeitumgebung', link: '<1> <2>Dokumentation der Sprache Visual Basic <3> <4> Lernen Sie X in Y Minuten, wobei X=Visual Basic <5> <6> LiveCodes Dokumentation <7> <8> Laden Sie die Startvorlage ', diff --git a/src/livecodes/i18n/locales/de/translation.lokalise.json b/src/livecodes/i18n/locales/de/translation.lokalise.json index 914ba2de33..9ad1cd6384 100644 --- a/src/livecodes/i18n/locales/de/translation.lokalise.json +++ b/src/livecodes/i18n/locales/de/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "TypeScript-Starter" }, + "templates.starter.v-wasm": { + "translation": "V-Starter" + }, "templates.starter.vb-wasm": { "translation": "VB.NET (Wasm)-Starter" }, diff --git a/src/livecodes/i18n/locales/de/translation.ts b/src/livecodes/i18n/locales/de/translation.ts index 915f79adf4..95cf87538c 100644 --- a/src/livecodes/i18n/locales/de/translation.ts +++ b/src/livecodes/i18n/locales/de/translation.ts @@ -1061,6 +1061,7 @@ const translation: I18nTranslation = { tcl: 'Tcl-Starter', teal: 'Teal-Starter', typescript: 'TypeScript-Starter', + 'v-wasm': 'V-Starter', 'vb-wasm': 'VB.NET (Wasm)-Starter', vue: 'Vue-Starter', vue2: 'Vue 2-Starter', diff --git a/src/livecodes/i18n/locales/en/language-info.lokalise.json b/src/livecodes/i18n/locales/en/language-info.lokalise.json index 987aaaa837..c15cb50fb3 100644 --- a/src/livecodes/i18n/locales/en/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/en/language-info.lokalise.json @@ -1216,6 +1216,18 @@ "notes": "", "translation": "TypeScript" }, + "v-wasm.desc": { + "notes": "", + "translation": "V compiler running on WebAssembly." + }, + "v-wasm.link": { + "notes": "### ###\n
  • \n\n### ###\n\n\n### ###\n
  • \n\n### ###\n\n\n### ###\n
  • \n\n### ###\n\n\n### ###\n
  • \n\n### ###\n\n\n### ###\n
  • \n\n### ###\n\n\n", + "translation": " V website V language documentation Learn X in Y minutes, where X=V LiveCodes Documentation Load starter template " + }, + "v-wasm.name": { + "notes": "", + "translation": "V (Wasm)" + }, "vbWasm.desc": { "notes": "### ###\n\n\n### ###\n\n\n", "translation": "VB.NET compiler running on WebAssembly, using Roslyn and the .NET WebAssembly runtime" diff --git a/src/livecodes/i18n/locales/en/language-info.ts b/src/livecodes/i18n/locales/en/language-info.ts index 0dc6d293bf..3d9bf197ec 100644 --- a/src/livecodes/i18n/locales/en/language-info.ts +++ b/src/livecodes/i18n/locales/en/language-info.ts @@ -521,6 +521,11 @@ const languageInfo = { link: '<1> <2>Official website <3> <4>TypeScript documentation <5> <6>Learn X in Y minutes, where X=TypeScript <7> <8>Load starter template ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'V compiler running on WebAssembly.', + link: '<1> <2>V website <3> <4>V language documentation <5> <6>Learn X in Y minutes, where X=V <7> <8>LiveCodes Documentation <9> <10>Load starter template ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'VB.NET compiler running on WebAssembly, using <1>Roslyn and the <2>.NET WebAssembly runtime', link: '<1> <2>Visual Basic language documentation <3> <4>Learn X in Y minutes, where X=Visual Basic <5> <6>LiveCodes Documentation <7> <8>Load starter template ', diff --git a/src/livecodes/i18n/locales/en/translation.lokalise.json b/src/livecodes/i18n/locales/en/translation.lokalise.json index e6f2d89824..795b9e52f0 100644 --- a/src/livecodes/i18n/locales/en/translation.lokalise.json +++ b/src/livecodes/i18n/locales/en/translation.lokalise.json @@ -2880,6 +2880,10 @@ "notes": "", "translation": "TypeScript Starter" }, + "templates.starter.v-wasm": { + "notes": "", + "translation": "V Starter" + }, "templates.starter.vb-wasm": { "notes": "", "translation": "VB.NET (Wasm) Starter" diff --git a/src/livecodes/i18n/locales/en/translation.ts b/src/livecodes/i18n/locales/en/translation.ts index 89958f0bde..bb85d63c7f 100644 --- a/src/livecodes/i18n/locales/en/translation.ts +++ b/src/livecodes/i18n/locales/en/translation.ts @@ -1066,6 +1066,7 @@ const translation = { tcl: 'Tcl Starter', teal: 'Teal Starter', typescript: 'TypeScript Starter', + 'v-wasm': 'V Starter', 'vb-wasm': 'VB.NET (Wasm) Starter', vue: 'Vue SFC Starter', vue2: 'Vue 2 Starter', diff --git a/src/livecodes/i18n/locales/es/language-info.lokalise.json b/src/livecodes/i18n/locales/es/language-info.lokalise.json index c4f43924b7..e09a488fc6 100644 --- a/src/livecodes/i18n/locales/es/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/es/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "Compilador V que se ejecuta en WebAssembly." + }, + "v-wasm.link": { + "translation": " Página web de V Documentación del lenguaje V Aprende X en Y minutos, donde X = V Documentación de LiveCodes Cargar plantilla de inicio " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "Compilador de VB.NET que funciona con WebAssembly, utilizando Roslyn y el entorno de ejecución de .NET para WebAssembly" }, diff --git a/src/livecodes/i18n/locales/es/language-info.ts b/src/livecodes/i18n/locales/es/language-info.ts index 1d3a0c5190..991e8c0690 100644 --- a/src/livecodes/i18n/locales/es/language-info.ts +++ b/src/livecodes/i18n/locales/es/language-info.ts @@ -515,6 +515,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>Sitio web oficial <3> <4>Documentación de TypeScript <5> <6>Aprende X en Y minutos, donde X=TypeScript <7> <8>Cargar plantilla inicial ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'Compilador V que se ejecuta en WebAssembly.', + link: '<1> <2>Página web de V <3> <4>Documentación del lenguaje V <5> <6>Aprende X en Y minutos, donde X = V <7> <8>Documentación de LiveCodes <9> <10>Cargar plantilla de inicio ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'Compilador de VB.NET que funciona con WebAssembly, utilizando <1>Roslyn y el <2>entorno de ejecución de .NET para WebAssembly', link: '<1> <2>Documentación del lenguaje Visual Basic <3> <4> Aprenda X en Y minutos, donde X=Visual Basic <5> <6> Documentación de LiveCodes <7> <8> Cargue la plantilla de inicio ', diff --git a/src/livecodes/i18n/locales/es/translation.lokalise.json b/src/livecodes/i18n/locales/es/translation.lokalise.json index 29545a4464..e8189c0806 100644 --- a/src/livecodes/i18n/locales/es/translation.lokalise.json +++ b/src/livecodes/i18n/locales/es/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "Inicio TypeScript" }, + "templates.starter.v-wasm": { + "translation": "Inicio V" + }, "templates.starter.vb-wasm": { "translation": "Inicio VB.NET (Wasm)" }, diff --git a/src/livecodes/i18n/locales/es/translation.ts b/src/livecodes/i18n/locales/es/translation.ts index 97cabf6951..1ce9aec126 100644 --- a/src/livecodes/i18n/locales/es/translation.ts +++ b/src/livecodes/i18n/locales/es/translation.ts @@ -1059,6 +1059,7 @@ const translation: I18nTranslation = { tcl: 'Inicio Tcl', teal: 'Inicio Teal', typescript: 'Inicio TypeScript', + 'v-wasm': 'Inicio V', 'vb-wasm': 'Inicio VB.NET (Wasm)', vue: 'Inicio Vue', vue2: 'Inicio Vue 2', diff --git a/src/livecodes/i18n/locales/fa/language-info.lokalise.json b/src/livecodes/i18n/locales/fa/language-info.lokalise.json index a64e8cd3c7..ab70c13dac 100644 --- a/src/livecodes/i18n/locales/fa/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/fa/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "کامپایلر V که روی WebAssembly اجرا می‌شود." + }, + "v-wasm.link": { + "translation": " وبسایت V مستندات زبان V زبان V را در چند دقیقه بیاموزید مستندات LiveCodes بارگذاری قالب شروع " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "کامپایلر VB.NET که روی WebAssembly اجرا می‌شود، با استفاده از Roslyn و محیط اجرایی .NET WebAssembly" }, diff --git a/src/livecodes/i18n/locales/fa/language-info.ts b/src/livecodes/i18n/locales/fa/language-info.ts index dea2e9dc89..83e21e8287 100644 --- a/src/livecodes/i18n/locales/fa/language-info.ts +++ b/src/livecodes/i18n/locales/fa/language-info.ts @@ -514,6 +514,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>وب‌سایت رسمی <3> <4>مستندات TypeScript <5> <6>یادگیری X در Y دقیقه، جایی که X=TypeScript <7> <8>بارگذاری قالب شروع کننده ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'کامپایلر V که روی WebAssembly اجرا می‌شود.', + link: '<1> <2>وبسایت V <3> <4>مستندات زبان V <5> <6>زبان V را در چند دقیقه بیاموزید <7> <8>مستندات LiveCodes <9> <10>بارگذاری قالب شروع ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'کامپایلر VB.NET که روی WebAssembly اجرا می‌شود، با استفاده از <1>Roslyn و <2>محیط اجرایی .NET WebAssembly', link: '<1> <2>مستندات زبان Visual Basic <3> <4>یادگیری X در Y دقیقه، جایی که X=Visual Basic <5> <6>مستندات LiveCodes <7> <8>بارگذاری قالب شروع کننده ', diff --git a/src/livecodes/i18n/locales/fa/translation.lokalise.json b/src/livecodes/i18n/locales/fa/translation.lokalise.json index 344ad63c46..ad42e57b33 100644 --- a/src/livecodes/i18n/locales/fa/translation.lokalise.json +++ b/src/livecodes/i18n/locales/fa/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "شروع کننده تایپ‌اسکریپت" }, + "templates.starter.v-wasm": { + "translation": "شروع کننده V" + }, "templates.starter.vb-wasm": { "translation": "شروع کننده VB.NET (Wasm)" }, diff --git a/src/livecodes/i18n/locales/fa/translation.ts b/src/livecodes/i18n/locales/fa/translation.ts index f51ed44098..f70134087e 100644 --- a/src/livecodes/i18n/locales/fa/translation.ts +++ b/src/livecodes/i18n/locales/fa/translation.ts @@ -1060,6 +1060,7 @@ const translation: I18nTranslation = { tcl: 'شروع کننده Tcl', teal: 'شروع کننده Teal', typescript: 'شروع کننده تایپ‌اسکریپت', + 'v-wasm': 'شروع کننده V', 'vb-wasm': 'شروع کننده VB.NET (Wasm)', vue: 'شروع کننده Vue SFC', vue2: 'شروع کننده Vue 2', diff --git a/src/livecodes/i18n/locales/fr/language-info.lokalise.json b/src/livecodes/i18n/locales/fr/language-info.lokalise.json index 140c8026a2..84a34baff5 100644 --- a/src/livecodes/i18n/locales/fr/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/fr/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "Compilateur V fonctionnant sur WebAssembly." + }, + "v-wasm.link": { + "translation": " Site web de V Documentation du langage V Apprenez X en Y minutes, où X = V Documentation LiveCodes Charger un modèle de démarrage " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "Compilateur VB.NET fonctionnant sur WebAssembly, utilisant Roslyn et le runtime .NET WebAssembly" }, diff --git a/src/livecodes/i18n/locales/fr/language-info.ts b/src/livecodes/i18n/locales/fr/language-info.ts index e9328b4798..d47d0cda0a 100644 --- a/src/livecodes/i18n/locales/fr/language-info.ts +++ b/src/livecodes/i18n/locales/fr/language-info.ts @@ -515,6 +515,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>Site officiel <3> <4>Documentation TypeScript <5> <6>Apprenez X en Y minutes, où X=TypeScript <7> <8>Charger le modèle de démarrage ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'Compilateur V fonctionnant sur WebAssembly.', + link: '<1> <2>Site web de V <3> <4>Documentation du langage V <5> <6>Apprenez X en Y minutes, où X = V <7> <8>Documentation LiveCodes <9> <10>Charger un modèle de démarrage ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'Compilateur VB.NET fonctionnant sur WebAssembly, utilisant <1>Roslyn et le <2>runtime .NET WebAssembly', link: '<1> <2>Documentation sur le langage Visual Basic <3> <4> Apprenez X en Y minutes, où X=Visual Basic <5> <6> Documentation LiveCodes <7> <8> Charger le modèle de démarrage ', diff --git a/src/livecodes/i18n/locales/fr/translation.lokalise.json b/src/livecodes/i18n/locales/fr/translation.lokalise.json index 4135e48ffd..1db7fa2a52 100644 --- a/src/livecodes/i18n/locales/fr/translation.lokalise.json +++ b/src/livecodes/i18n/locales/fr/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "Démarrage TypeScript" }, + "templates.starter.v-wasm": { + "translation": "Démarrage V" + }, "templates.starter.vb-wasm": { "translation": "Démarrage VB.NET (Wasm)" }, diff --git a/src/livecodes/i18n/locales/fr/translation.ts b/src/livecodes/i18n/locales/fr/translation.ts index 6d083ee2b2..5424a4a20d 100644 --- a/src/livecodes/i18n/locales/fr/translation.ts +++ b/src/livecodes/i18n/locales/fr/translation.ts @@ -1061,6 +1061,7 @@ const translation: I18nTranslation = { tcl: 'Démarrage Tcl', teal: 'Démarrage Teal', typescript: 'Démarrage TypeScript', + 'v-wasm': 'Démarrage V', 'vb-wasm': 'Démarrage VB.NET (Wasm)', vue: 'Démarrage Vue', vue2: 'Démarrage Vue 2', diff --git a/src/livecodes/i18n/locales/hi/language-info.lokalise.json b/src/livecodes/i18n/locales/hi/language-info.lokalise.json index f280849b4d..1f0ec67905 100644 --- a/src/livecodes/i18n/locales/hi/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/hi/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "वेबअसेंबली पर चल रहा V कंपाइलर।" + }, + "v-wasm.link": { + "translation": " V वेबसाइट V भाषा दस्तावेज़ीकरण वाई मिनटों में एक्स सीखें, जहां एक्स=V लाइवकोड दस्तावेज़ीकरण स्टार्टर टेम्पलेट लोड करें " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "VB.NET कंपाइलर WebAssembly पर चल रहा है, Roslyn और .NET वेबअसेंबली रनटाइम का उपयोग करके" }, diff --git a/src/livecodes/i18n/locales/hi/language-info.ts b/src/livecodes/i18n/locales/hi/language-info.ts index 2f6cb47781..e5cfcd00e1 100644 --- a/src/livecodes/i18n/locales/hi/language-info.ts +++ b/src/livecodes/i18n/locales/hi/language-info.ts @@ -516,6 +516,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>आधिकारिक वेबसाइट <3> <4>TypeScript दस्तावेज़ीकरण <5> <6>X को Y मिनटों में सीखें, जहां X=TypeScript <7> <8>स्टार्टर टेम्पलेट लोड करें ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'वेबअसेंबली पर चल रहा V कंपाइलर।', + link: '<1> <2>V वेबसाइट <3> <4>V भाषा दस्तावेज़ीकरण <5> <6>वाई मिनटों में एक्स सीखें, जहां एक्स=V <7> <8>लाइवकोड दस्तावेज़ीकरण <9> <10>स्टार्टर टेम्पलेट लोड करें ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'VB.NET कंपाइलर WebAssembly पर चल रहा है, <1>Roslyn और <2>.NET वेबअसेंबली रनटाइम का उपयोग करके', link: '<1> <2>Visual Basic भाषा दस्तावेज़ीकरण <3> <4>एक्स को वाई मिनटों में सीखें, जहां एक्स=Visual Basic <5> <6>लाइवकोड दस्तावेज़ीकरण <7> <8>स्टार्टर टेम्पलेट लोड करें ', diff --git a/src/livecodes/i18n/locales/hi/translation.lokalise.json b/src/livecodes/i18n/locales/hi/translation.lokalise.json index c05ad0a63e..e9f6673e51 100644 --- a/src/livecodes/i18n/locales/hi/translation.lokalise.json +++ b/src/livecodes/i18n/locales/hi/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "TypeScript स्टार्टर" }, + "templates.starter.v-wasm": { + "translation": "V स्टार्टर" + }, "templates.starter.vb-wasm": { "translation": "VB.NET (Wasm) स्टार्टर" }, diff --git a/src/livecodes/i18n/locales/hi/translation.ts b/src/livecodes/i18n/locales/hi/translation.ts index 2120fab325..a869e4b1ee 100644 --- a/src/livecodes/i18n/locales/hi/translation.ts +++ b/src/livecodes/i18n/locales/hi/translation.ts @@ -1059,6 +1059,7 @@ const translation: I18nTranslation = { tcl: 'Tcl स्टार्टर', teal: 'Teal स्टार्टर', typescript: 'TypeScript स्टार्टर', + 'v-wasm': 'V स्टार्टर', 'vb-wasm': 'VB.NET (Wasm) स्टार्टर', vue: 'Vue स्टार्टर', vue2: 'Vue 2 स्टार्टर', diff --git a/src/livecodes/i18n/locales/hu/language-info.lokalise.json b/src/livecodes/i18n/locales/hu/language-info.lokalise.json index 84eac14e02..15debc8b58 100644 --- a/src/livecodes/i18n/locales/hu/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/hu/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "A V fordító WebAssembly-en fut." + }, + "v-wasm.link": { + "translation": " A V weboldala A V nyelv dokumentációja Tanulj meg X-et Y perc alatt, ahol X=V LiveCodes dokumentáció Indító sablon betöltése " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "VB.NET fordító WebAssembly-n futva, a Roslyn és a .NET WebAssembly futtatókörnyezet használatával" }, diff --git a/src/livecodes/i18n/locales/hu/language-info.ts b/src/livecodes/i18n/locales/hu/language-info.ts index 6e8d4606e2..107be26215 100644 --- a/src/livecodes/i18n/locales/hu/language-info.ts +++ b/src/livecodes/i18n/locales/hu/language-info.ts @@ -515,6 +515,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>Hivatalos weboldal <3> <4>TypeScript dokumentáció <5> <6>Tanulj X-et Y perc alatt, ahol X=TypeScript <7> <8>Kezdő sablon betöltése ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'A V fordító WebAssembly-en fut.', + link: '<1> <2>A V weboldala <3> <4>A V nyelv dokumentációja <5> <6>Tanulj meg X-et Y perc alatt, ahol X=V <7> <8>LiveCodes dokumentáció <9> <10>Indító sablon betöltése ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'VB.NET fordító WebAssembly-n futva, a <1>Roslyn és a <2>.NET WebAssembly futtatókörnyezet használatával', link: '<1> <2>Visual Basic nyelvi dokumentáció <3> <4>Tanulj X-et Y perc alatt, ahol X=Visual Basic <5> <6>LiveCodes dokumentáció <7> <8>Kezdő sablon betöltése ', diff --git a/src/livecodes/i18n/locales/hu/translation.lokalise.json b/src/livecodes/i18n/locales/hu/translation.lokalise.json index f46a2869c1..111fde9cd6 100644 --- a/src/livecodes/i18n/locales/hu/translation.lokalise.json +++ b/src/livecodes/i18n/locales/hu/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "TypeScript kezdő" }, + "templates.starter.v-wasm": { + "translation": "V kezdő" + }, "templates.starter.vb-wasm": { "translation": "VB.NET (Wasm) kezdő" }, diff --git a/src/livecodes/i18n/locales/hu/translation.ts b/src/livecodes/i18n/locales/hu/translation.ts index dcb1301048..96feadcee3 100644 --- a/src/livecodes/i18n/locales/hu/translation.ts +++ b/src/livecodes/i18n/locales/hu/translation.ts @@ -1060,6 +1060,7 @@ const translation: I18nTranslation = { tcl: 'Tcl kezdő', teal: 'Teal kezdő', typescript: 'TypeScript kezdő', + 'v-wasm': 'V kezdő', 'vb-wasm': 'VB.NET (Wasm) kezdő', vue: 'Vue SFC kezdő', vue2: 'Vue 2 kezdő', diff --git a/src/livecodes/i18n/locales/id/language-info.lokalise.json b/src/livecodes/i18n/locales/id/language-info.lokalise.json index c5a075af70..de44c91fd2 100644 --- a/src/livecodes/i18n/locales/id/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/id/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "Kompiler V yang berjalan di WebAssembly." + }, + "v-wasm.link": { + "translation": " Situs web V Dokumentasi bahasa V Pelajari X dalam Y menit, di mana X = V Dokumentasi LiveCodes Muat templat awal " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "Kompiler VB.NET berjalan di WebAssembly, menggunakan Roslyn dan runtime .NET WebAssembly" }, diff --git a/src/livecodes/i18n/locales/id/language-info.ts b/src/livecodes/i18n/locales/id/language-info.ts index 9a24feb09e..cbc949f82e 100644 --- a/src/livecodes/i18n/locales/id/language-info.ts +++ b/src/livecodes/i18n/locales/id/language-info.ts @@ -515,6 +515,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>Situs web resmi <3> <4>Dokumentasi TypeScript <5> <6>Pelajari X dalam Y menit, dengan X=TypeScript <7> <8>Muat template pemula ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'Kompiler V yang berjalan di WebAssembly.', + link: '<1> <2>Situs web V <3> <4>Dokumentasi bahasa V <5> <6>Pelajari X dalam Y menit, di mana X = V <7> <8>Dokumentasi LiveCodes <9> <10>Muat templat awal ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'Kompiler VB.NET berjalan di WebAssembly, menggunakan <1>Roslyn dan <2>runtime .NET WebAssembly', link: '<1> <2>Dokumentasi bahasa Visual Basic <3> <4>Pelajari X dalam Y menit, dengan X=Visual Basic <5> <6>Dokumentasi LiveCodes <7> <8>Muat template pemula ', diff --git a/src/livecodes/i18n/locales/id/translation.lokalise.json b/src/livecodes/i18n/locales/id/translation.lokalise.json index 6d0aa7c2be..b5a00bac5f 100644 --- a/src/livecodes/i18n/locales/id/translation.lokalise.json +++ b/src/livecodes/i18n/locales/id/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "Pemula TypeScript" }, + "templates.starter.v-wasm": { + "translation": "Pemula V" + }, "templates.starter.vb-wasm": { "translation": "Pemula VB.NET (Wasm)" }, diff --git a/src/livecodes/i18n/locales/id/translation.ts b/src/livecodes/i18n/locales/id/translation.ts index ad70da0152..1a67e1e656 100644 --- a/src/livecodes/i18n/locales/id/translation.ts +++ b/src/livecodes/i18n/locales/id/translation.ts @@ -1059,6 +1059,7 @@ const translation: I18nTranslation = { tcl: 'Pemula Tcl', teal: 'Pemula Teal', typescript: 'Pemula TypeScript', + 'v-wasm': 'Pemula V', 'vb-wasm': 'Pemula VB.NET (Wasm)', vue: 'Pemula Vue SFC', vue2: 'Pemula Vue 2', diff --git a/src/livecodes/i18n/locales/it/language-info.lokalise.json b/src/livecodes/i18n/locales/it/language-info.lokalise.json index 34bba3d623..33d9f87e58 100644 --- a/src/livecodes/i18n/locales/it/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/it/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "Compilatore V in esecuzione su WebAssembly." + }, + "v-wasm.link": { + "translation": " Sito web di V Documentazione sul linguaggio V Impara X in Y minuti, dove X = V Documentazione LiveCodes Carica il modello di avvio " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "Compilatore VB.NET in esecuzione su WebAssembly, utilizzando Roslyn e il runtime .NET per WebAssembly" }, diff --git a/src/livecodes/i18n/locales/it/language-info.ts b/src/livecodes/i18n/locales/it/language-info.ts index 09bad4dc6b..10bb9e2544 100644 --- a/src/livecodes/i18n/locales/it/language-info.ts +++ b/src/livecodes/i18n/locales/it/language-info.ts @@ -514,6 +514,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>Sito ufficiale <3> <4>Documentazione di TypeScript <5> <6>Impara X in Y minuti, dove X=TypeScript <7> <8>Carica modello di partenza ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'Compilatore V in esecuzione su WebAssembly.', + link: '<1> <2>Sito web di V <3> <4>Documentazione sul linguaggio V <5> <6>Impara X in Y minuti, dove X = V <7> <8>Documentazione LiveCodes <9> <10>Carica il modello di avvio ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'Compilatore VB.NET in esecuzione su WebAssembly, utilizzando <1>Roslyn e il <2>runtime .NET per WebAssembly', link: '<1> <2>Documentazione sul linguaggio Visual Basic <3> <4> Imparare X in Y minuti, dove X=Visual Basic <5> <6> Documentazione LiveCodes <7> <8> Carica il modello iniziale ', diff --git a/src/livecodes/i18n/locales/it/translation.lokalise.json b/src/livecodes/i18n/locales/it/translation.lokalise.json index 816e747b3a..62b8230178 100644 --- a/src/livecodes/i18n/locales/it/translation.lokalise.json +++ b/src/livecodes/i18n/locales/it/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "Starter TypeScript" }, + "templates.starter.v-wasm": { + "translation": "Starter V" + }, "templates.starter.vb-wasm": { "translation": "Starter VB.NET (Wasm)" }, diff --git a/src/livecodes/i18n/locales/it/translation.ts b/src/livecodes/i18n/locales/it/translation.ts index 00d5a12ec9..136413ab2d 100644 --- a/src/livecodes/i18n/locales/it/translation.ts +++ b/src/livecodes/i18n/locales/it/translation.ts @@ -1060,6 +1060,7 @@ const translation: I18nTranslation = { tcl: 'Starter Tcl', teal: 'Starter Teal', typescript: 'Starter TypeScript', + 'v-wasm': 'Starter V', 'vb-wasm': 'Starter VB.NET (Wasm)', vue: 'Starter Vue', vue2: 'Starter Vue 2', diff --git a/src/livecodes/i18n/locales/ja/language-info.lokalise.json b/src/livecodes/i18n/locales/ja/language-info.lokalise.json index 1b50161e6a..b6cc53d4b3 100644 --- a/src/livecodes/i18n/locales/ja/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/ja/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "WebAssembly上で動作するVコンパイラ。" + }, + "v-wasm.link": { + "translation": " Vのウェブサイト V言語のドキュメント 「XをY分で学ぶ」(X=V) LiveCodes ドキュメント スターターテンプレートを読み込む " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "WebAssembly 上で動作する VB.NET コンパイラ。Roslyn と .NET WebAssembly ランタイム を使用。" }, diff --git a/src/livecodes/i18n/locales/ja/language-info.ts b/src/livecodes/i18n/locales/ja/language-info.ts index eb625fdb27..7310680931 100644 --- a/src/livecodes/i18n/locales/ja/language-info.ts +++ b/src/livecodes/i18n/locales/ja/language-info.ts @@ -515,6 +515,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>公式ウェブサイト <3> <4>TypeScript ドキュメント <5> <6>X分でYを学ぶ、X=TypeScript <7> <8>スターターテンプレートを読み込む ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'WebAssembly上で動作するVコンパイラ。', + link: '<1> <2>Vのウェブサイト <3> <4>V言語のドキュメント <5> <6>「XをY分で学ぶ」(X=V) <7> <8>LiveCodes ドキュメント <9> <10>スターターテンプレートを読み込む ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'WebAssembly 上で動作する VB.NET コンパイラ。<1>Roslyn と <2>.NET WebAssembly ランタイム を使用。', link: '<1> <2>Visual Basic 言語のドキュメント <3> <4>X を Y 分で学習します (X=Visual Basic の場合) <5> <6>LiveCodesドキュメンテーション <7> <8>スターターテンプレートをロードする ', diff --git a/src/livecodes/i18n/locales/ja/translation.lokalise.json b/src/livecodes/i18n/locales/ja/translation.lokalise.json index 43ae4dbe59..46230746f0 100644 --- a/src/livecodes/i18n/locales/ja/translation.lokalise.json +++ b/src/livecodes/i18n/locales/ja/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "TypeScriptスターター" }, + "templates.starter.v-wasm": { + "translation": "Vスターター" + }, "templates.starter.vb-wasm": { "translation": "VB.NET(Wasm)スターター" }, diff --git a/src/livecodes/i18n/locales/ja/translation.ts b/src/livecodes/i18n/locales/ja/translation.ts index 152287864c..b4b308f466 100644 --- a/src/livecodes/i18n/locales/ja/translation.ts +++ b/src/livecodes/i18n/locales/ja/translation.ts @@ -1060,6 +1060,7 @@ const translation: I18nTranslation = { tcl: 'Tclスターター', teal: 'Tealスターター', typescript: 'TypeScriptスターター', + 'v-wasm': 'Vスターター', 'vb-wasm': 'VB.NET(Wasm)スターター', vue: 'Vue スターター', vue2: 'Vue 2スターター', diff --git a/src/livecodes/i18n/locales/nl/language-info.lokalise.json b/src/livecodes/i18n/locales/nl/language-info.lokalise.json index 1f5f1f62f6..57886e5b0d 100644 --- a/src/livecodes/i18n/locales/nl/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/nl/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "V-compiler die op WebAssembly draait." + }, + "v-wasm.link": { + "translation": " V-website Documentatie over de taal V Leer X in Y minuten, waarbij X = V LiveCodes-documentatie Starterssjabloon laden " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "VB.NET-compiler draaiend op WebAssembly, met Roslyn en de .NET WebAssembly-runtime" }, diff --git a/src/livecodes/i18n/locales/nl/language-info.ts b/src/livecodes/i18n/locales/nl/language-info.ts index e1f714ea1c..c15a2940a5 100644 --- a/src/livecodes/i18n/locales/nl/language-info.ts +++ b/src/livecodes/i18n/locales/nl/language-info.ts @@ -514,6 +514,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>Officiële website <3> <4>TypeScript documentatie <5> <6>Leer X in Y minuten, waarbij X=TypeScript <7> <8>Startsjabloon laden ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'V-compiler die op WebAssembly draait.', + link: '<1> <2>V-website <3> <4>Documentatie over de taal V <5> <6>Leer X in Y minuten, waarbij X = V <7> <8>LiveCodes-documentatie <9> <10>Starterssjabloon laden ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'VB.NET-compiler draaiend op WebAssembly, met <1>Roslyn en de <2>.NET WebAssembly-runtime', link: '<1> <2>Visual Basic-taaldocumentatie <3> <4>Leer X in Y minuten, waarbij X=Visual Basic <5> <6>LiveCodes Documentatie <7> <8>Startsjabloon laden ', diff --git a/src/livecodes/i18n/locales/nl/translation.lokalise.json b/src/livecodes/i18n/locales/nl/translation.lokalise.json index 1a2cf5bea2..0f1427d739 100644 --- a/src/livecodes/i18n/locales/nl/translation.lokalise.json +++ b/src/livecodes/i18n/locales/nl/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "TypeScript-starter" }, + "templates.starter.v-wasm": { + "translation": "V-starter" + }, "templates.starter.vb-wasm": { "translation": "VB.NET (Wasm)-starter" }, diff --git a/src/livecodes/i18n/locales/nl/translation.ts b/src/livecodes/i18n/locales/nl/translation.ts index 7aac8a6a1a..525256982c 100644 --- a/src/livecodes/i18n/locales/nl/translation.ts +++ b/src/livecodes/i18n/locales/nl/translation.ts @@ -1060,6 +1060,7 @@ const translation: I18nTranslation = { tcl: 'Tcl-starter', teal: 'Teal-starter', typescript: 'TypeScript-starter', + 'v-wasm': 'V-starter', 'vb-wasm': 'VB.NET (Wasm)-starter', vue: 'Vue SFC-starter', vue2: 'Vue 2-starter', diff --git a/src/livecodes/i18n/locales/pt/language-info.lokalise.json b/src/livecodes/i18n/locales/pt/language-info.lokalise.json index 32cd91fd32..5cd84d86f5 100644 --- a/src/livecodes/i18n/locales/pt/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/pt/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "Compilador V a funcionar no WebAssembly." + }, + "v-wasm.link": { + "translation": " Site do V Documentação da linguagem V Aprenda X em Y minutos, em que X = V Documentação do LiveCodes Carregar modelo inicial " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "Compilador VB.NET executado no WebAssembly, usando Roslyn e o ambiente de execução .NET para WebAssembly" }, diff --git a/src/livecodes/i18n/locales/pt/language-info.ts b/src/livecodes/i18n/locales/pt/language-info.ts index a79cf64a9c..7366e7d092 100644 --- a/src/livecodes/i18n/locales/pt/language-info.ts +++ b/src/livecodes/i18n/locales/pt/language-info.ts @@ -514,6 +514,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>Site oficial <3> <4>Documentação do TypeScript <5> <6>Aprenda X em Y minutos, onde X=TypeScript <7> <8>Carregar modelo inicial ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'Compilador V a funcionar no WebAssembly.', + link: '<1> <2>Site do V <3> <4>Documentação da linguagem V <5> <6>Aprenda X em Y minutos, em que X = V <7> <8>Documentação do LiveCodes <9> <10>Carregar modelo inicial ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'Compilador VB.NET executado no WebAssembly, usando <1>Roslyn e o <2>ambiente de execução .NET para WebAssembly', link: '<1> <2>Documentação da linguagem Visual Basic <3> <4> Aprenda X em Y minutos, onde X=Visual Basic <5> <6> Documentação LiveCodes <7> <8> Carregar modelo inicial ', diff --git a/src/livecodes/i18n/locales/pt/translation.lokalise.json b/src/livecodes/i18n/locales/pt/translation.lokalise.json index 9240d000f2..6b8a7642c3 100644 --- a/src/livecodes/i18n/locales/pt/translation.lokalise.json +++ b/src/livecodes/i18n/locales/pt/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "Iniciante TypeScript" }, + "templates.starter.v-wasm": { + "translation": "Iniciante V" + }, "templates.starter.vb-wasm": { "translation": "Iniciante VB.NET (Wasm)" }, diff --git a/src/livecodes/i18n/locales/pt/translation.ts b/src/livecodes/i18n/locales/pt/translation.ts index e068a9419b..85ab70f0cc 100644 --- a/src/livecodes/i18n/locales/pt/translation.ts +++ b/src/livecodes/i18n/locales/pt/translation.ts @@ -1060,6 +1060,7 @@ const translation: I18nTranslation = { tcl: 'Iniciante Tcl', teal: 'Iniciante Teal', typescript: 'Iniciante TypeScript', + 'v-wasm': 'Iniciante V', 'vb-wasm': 'Iniciante VB.NET (Wasm)', vue: 'Iniciante Vue', vue2: 'Iniciante Vue 2', diff --git a/src/livecodes/i18n/locales/ru/language-info.lokalise.json b/src/livecodes/i18n/locales/ru/language-info.lokalise.json index 36f603eb61..e6afaab961 100644 --- a/src/livecodes/i18n/locales/ru/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/ru/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "Компилятор V, работающий на WebAssembly." + }, + "v-wasm.link": { + "translation": " Сайт V Документация по языку V Изучите X за Y минут, где X = V Документация LiveCodes Загрузить стартовый шаблон " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "Компилятор VB.NET, работающий на WebAssembly, с использованием Roslyn и среды выполнения .NET для WebAssembly" }, diff --git a/src/livecodes/i18n/locales/ru/language-info.ts b/src/livecodes/i18n/locales/ru/language-info.ts index b40fa609d1..7f44eae380 100644 --- a/src/livecodes/i18n/locales/ru/language-info.ts +++ b/src/livecodes/i18n/locales/ru/language-info.ts @@ -516,6 +516,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>Официальный сайт <3> <4>Документация TypeScript <5> <6>Изучите X за Y минут, где X=TypeScript <7> <8>Загрузить стартовый шаблон ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'Компилятор V, работающий на WebAssembly.', + link: '<1> <2>Сайт V <3> <4>Документация по языку V <5> <6>Изучите X за Y минут, где X = V <7> <8>Документация LiveCodes <9> <10>Загрузить стартовый шаблон ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'Компилятор VB.NET, работающий на WebAssembly, с использованием <1>Roslyn и <2>среды выполнения .NET для WebAssembly', link: '<1> <2>Документация по языку Visual Basic <3> <4> Узнайте X за Y минут, где X=Visual Basic <5> <6> Документация LiveCodes <7> <8> Загрузите стартовый шаблон ', diff --git a/src/livecodes/i18n/locales/ru/translation.lokalise.json b/src/livecodes/i18n/locales/ru/translation.lokalise.json index 4a0004d386..4d10a6cc62 100644 --- a/src/livecodes/i18n/locales/ru/translation.lokalise.json +++ b/src/livecodes/i18n/locales/ru/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "Стартер TypeScript" }, + "templates.starter.v-wasm": { + "translation": "Стартер V" + }, "templates.starter.vb-wasm": { "translation": "Стартер VB.NET (Wasm)" }, diff --git a/src/livecodes/i18n/locales/ru/translation.ts b/src/livecodes/i18n/locales/ru/translation.ts index 0073f09164..160e24e781 100644 --- a/src/livecodes/i18n/locales/ru/translation.ts +++ b/src/livecodes/i18n/locales/ru/translation.ts @@ -1059,6 +1059,7 @@ const translation: I18nTranslation = { tcl: 'Стартер Tcl', teal: 'Стартер Teal', typescript: 'Стартер TypeScript', + 'v-wasm': 'Стартер V', 'vb-wasm': 'Стартер VB.NET (Wasm)', vue: 'Стартер Vue', vue2: 'Стартер Vue 2', diff --git a/src/livecodes/i18n/locales/tr/language-info.lokalise.json b/src/livecodes/i18n/locales/tr/language-info.lokalise.json index 935c311b93..954a17d49b 100644 --- a/src/livecodes/i18n/locales/tr/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/tr/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "WebAssembly üzerinde çalışan V derleyicisi." + }, + "v-wasm.link": { + "translation": " V web sitesi V dil belgeleri Y dakikada X'i öğrenin, burada X=V LiveCodes Belgeleri Başlangıç şablonunu yükle " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "WebAssembly üzerinde çalışan VB.NET derleyicisi, Roslyn ve .NET WebAssembly çalışma zamanı kullanılarak." }, diff --git a/src/livecodes/i18n/locales/tr/language-info.ts b/src/livecodes/i18n/locales/tr/language-info.ts index aac52ba913..11148f60ae 100644 --- a/src/livecodes/i18n/locales/tr/language-info.ts +++ b/src/livecodes/i18n/locales/tr/language-info.ts @@ -515,6 +515,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>Resmi web sitesi <3> <4>TypeScript dokümantasyonu <5> <6>Y Dakikada X Öğrenin, X=TypeScript <7> <8>Başlangıç şablonunu yükle ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'WebAssembly üzerinde çalışan V derleyicisi.', + link: "<1> <2>V web sitesi <3> <4>V dil belgeleri <5> <6>Y dakikada X'i öğrenin, burada X=V <7> <8>LiveCodes Belgeleri <9> <10>Başlangıç şablonunu yükle ", + name: 'V (Wasm)', + }, vbWasm: { desc: 'WebAssembly üzerinde çalışan VB.NET derleyicisi, <1>Roslyn ve <2>.NET WebAssembly çalışma zamanı kullanılarak.', link: '<1> <2>Visual Basic dil dokümantasyonu <3> <4>Y Dakikada X Öğrenin, X=Visual Basic <5> <6>LiveCodes Dokümantasyonu <7> <8>Başlangıç şablonunu yükle ', diff --git a/src/livecodes/i18n/locales/tr/translation.lokalise.json b/src/livecodes/i18n/locales/tr/translation.lokalise.json index 9d8bcbdd5d..07e4d62715 100644 --- a/src/livecodes/i18n/locales/tr/translation.lokalise.json +++ b/src/livecodes/i18n/locales/tr/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "TypeScript Başlangıç" }, + "templates.starter.v-wasm": { + "translation": "V Başlangıç" + }, "templates.starter.vb-wasm": { "translation": "VB.NET (Wasm) Başlangıç" }, diff --git a/src/livecodes/i18n/locales/tr/translation.ts b/src/livecodes/i18n/locales/tr/translation.ts index 3cc5c451b0..8613a9e5d1 100644 --- a/src/livecodes/i18n/locales/tr/translation.ts +++ b/src/livecodes/i18n/locales/tr/translation.ts @@ -1060,6 +1060,7 @@ const translation: I18nTranslation = { tcl: 'Tcl Başlangıç', teal: 'Teal Başlangıç', typescript: 'TypeScript Başlangıç', + 'v-wasm': 'V Başlangıç', 'vb-wasm': 'VB.NET (Wasm) Başlangıç', vue: 'Vue SFC Başlangıç', vue2: 'Vue 2 Başlangıç', diff --git a/src/livecodes/i18n/locales/ur/language-info.lokalise.json b/src/livecodes/i18n/locales/ur/language-info.lokalise.json index 4c7705337a..e4edd1524e 100644 --- a/src/livecodes/i18n/locales/ur/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/ur/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "V کمپائلر WebAssembly پر چل رہا ہے۔" + }, + "v-wasm.link": { + "translation": " V ویب سائٹ V زبان کی دستاویزات X=V — V کو چند منٹوں میں سیکھیں LiveCodes دستاویزات اسٹارٹر ٹیمپلیٹ لوڈ کریں " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "VB.NET کمپائلر ویب اسمبلی پر چل رہا ہے، Roslyn اور .NET WebAssembly رن ٹائم استعمال کر رہا ہے" }, diff --git a/src/livecodes/i18n/locales/ur/language-info.ts b/src/livecodes/i18n/locales/ur/language-info.ts index 84355149fa..542bb40624 100644 --- a/src/livecodes/i18n/locales/ur/language-info.ts +++ b/src/livecodes/i18n/locales/ur/language-info.ts @@ -517,6 +517,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>سرکاری ویب سائٹ <3> <4>TypeScript دستاویزات <5> <6>X کو Y منٹ میں سیکھیں، جہاں X=TypeScript <7> <8>شروعاتی سانچہ لوڈ کریں ', name: 'TypeScript', }, + 'v-wasm': { + desc: 'V کمپائلر WebAssembly پر چل رہا ہے۔', + link: '<1> <2>V ویب سائٹ <3> <4>V زبان کی دستاویزات <5> <6>X=V — V کو چند منٹوں میں سیکھیں <7> <8>LiveCodes دستاویزات <9> <10>اسٹارٹر ٹیمپلیٹ لوڈ کریں ', + name: 'V (Wasm)', + }, vbWasm: { desc: 'VB.NET کمپائلر ویب اسمبلی پر چل رہا ہے، <1>Roslyn اور <2>.NET WebAssembly رن ٹائم استعمال کر رہا ہے', link: '<1> <2> Visual Basic زبان کی دستاویزات <3> <4> Y منٹوں میں X سیکھیں، جہاں X=Visual Basic <5> <6> LiveCodes ڈاکومینٹیشن <7> <8> اسٹارٹر ٹیمپلیٹ لوڈ کریں ', diff --git a/src/livecodes/i18n/locales/ur/translation.lokalise.json b/src/livecodes/i18n/locales/ur/translation.lokalise.json index 6abab68d77..f6c5b0d2c5 100644 --- a/src/livecodes/i18n/locales/ur/translation.lokalise.json +++ b/src/livecodes/i18n/locales/ur/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "ٹائپ اسکرپٹ شروعاتی" }, + "templates.starter.v-wasm": { + "translation": "V شروعاتی" + }, "templates.starter.vb-wasm": { "translation": "VB.NET (واسم) شروعاتی" }, diff --git a/src/livecodes/i18n/locales/ur/translation.ts b/src/livecodes/i18n/locales/ur/translation.ts index 592a0d7d93..7231548fc8 100644 --- a/src/livecodes/i18n/locales/ur/translation.ts +++ b/src/livecodes/i18n/locales/ur/translation.ts @@ -1059,6 +1059,7 @@ const translation: I18nTranslation = { tcl: 'ٹی سی ایل شروعاتی', teal: 'ٹیل شروعاتی', typescript: 'ٹائپ اسکرپٹ شروعاتی', + 'v-wasm': 'V شروعاتی', 'vb-wasm': 'VB.NET (واسم) شروعاتی', vue: 'ویو شروعاتی', vue2: 'ویو 2 شروعاتی', diff --git a/src/livecodes/i18n/locales/zh-CN/language-info.lokalise.json b/src/livecodes/i18n/locales/zh-CN/language-info.lokalise.json index cb11cf9eb3..7441fdf48a 100644 --- a/src/livecodes/i18n/locales/zh-CN/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/zh-CN/language-info.lokalise.json @@ -912,6 +912,15 @@ "typescript.name": { "translation": "TypeScript" }, + "v-wasm.desc": { + "translation": "在 WebAssembly 上运行的 V 编译器。" + }, + "v-wasm.link": { + "translation": " V 网站 V 语言文档 Y分钟掌握X,其中X=V LiveCodes 文档 加载入门模板 " + }, + "v-wasm.name": { + "translation": "V (Wasm)" + }, "vbWasm.desc": { "translation": "在 WebAssembly 上运行的 VB.NET 编译器,使用 Roslyn 和 .NET WebAssembly 运行时" }, diff --git a/src/livecodes/i18n/locales/zh-CN/language-info.ts b/src/livecodes/i18n/locales/zh-CN/language-info.ts index 400ddd621a..8f1b743c51 100644 --- a/src/livecodes/i18n/locales/zh-CN/language-info.ts +++ b/src/livecodes/i18n/locales/zh-CN/language-info.ts @@ -508,6 +508,11 @@ const languageInfo: I18nLangInfoTranslation = { link: '<1> <2>官方网站 <3> <4>TypeScript 文档 <5> <6>在 Y 分钟内学习 X,其中 X=TypeScript <7> <8>加载启动模板 ', name: 'TypeScript', }, + 'v-wasm': { + desc: '在 WebAssembly 上运行的 V 编译器。', + link: '<1> <2>V 网站 <3> <4>V 语言文档 <5> <6>Y分钟掌握X,其中X=V <7> <8>LiveCodes 文档 <9> <10>加载入门模板 ', + name: 'V (Wasm)', + }, vbWasm: { desc: '在 WebAssembly 上运行的 VB.NET 编译器,使用 <1>Roslyn 和 <2>.NET WebAssembly 运行时', link: '<1> <2> Visual Basic 语言文档 <3> <4>在 Y 分钟内学习 X,其中 X=Visual Basic <5> <6> LiveCodes 文档 <7> <8>加载入门模板 ', diff --git a/src/livecodes/i18n/locales/zh-CN/translation.lokalise.json b/src/livecodes/i18n/locales/zh-CN/translation.lokalise.json index 360d3eb3dc..e24af0a22a 100644 --- a/src/livecodes/i18n/locales/zh-CN/translation.lokalise.json +++ b/src/livecodes/i18n/locales/zh-CN/translation.lokalise.json @@ -2160,6 +2160,9 @@ "templates.starter.typescript": { "translation": "TypeScript 启动模板" }, + "templates.starter.v-wasm": { + "translation": "V 启动模板" + }, "templates.starter.vb-wasm": { "translation": "VB.NET(WASM)启动模板" }, diff --git a/src/livecodes/i18n/locales/zh-CN/translation.ts b/src/livecodes/i18n/locales/zh-CN/translation.ts index 759aa9540b..02b0401d48 100644 --- a/src/livecodes/i18n/locales/zh-CN/translation.ts +++ b/src/livecodes/i18n/locales/zh-CN/translation.ts @@ -1057,6 +1057,7 @@ const translation: I18nTranslation = { tcl: 'Tcl 启动模板', teal: 'Teal 启动模板', typescript: 'TypeScript 启动模板', + 'v-wasm': 'V 启动模板', 'vb-wasm': 'VB.NET(WASM)启动模板', vue: 'Vue 单文件组件启动模板', vue2: 'Vue 2 启动模板', diff --git a/src/livecodes/languages/languages.ts b/src/livecodes/languages/languages.ts index 47e4e63a1e..105e5f7fa7 100644 --- a/src/livecodes/languages/languages.ts +++ b/src/livecodes/languages/languages.ts @@ -90,6 +90,7 @@ import { tcl } from './tcl'; import { teal } from './teal'; import { twig } from './twig'; import { typescript } from './typescript'; +import { vWasm } from './v-wasm'; import { vbWasm } from './vb-wasm'; import { vento } from './vento'; import { vue, vueApp } from './vue'; @@ -177,6 +178,7 @@ export const languages: LanguageSpecs[] = [ java, csharpWasm, blazorWasm, + vWasm, vbWasm, fsharp, fsharpWasm, diff --git a/src/livecodes/languages/v-wasm/__tests__/lang-v-wasm.spec.ts b/src/livecodes/languages/v-wasm/__tests__/lang-v-wasm.spec.ts new file mode 100644 index 0000000000..c89cfb163b --- /dev/null +++ b/src/livecodes/languages/v-wasm/__tests__/lang-v-wasm.spec.ts @@ -0,0 +1,32 @@ +import { vWasm } from '../lang-v-wasm'; + +describe('V (Wasm) language spec', () => { + test('is registered as v-wasm, with the wasm compiler script', () => { + expect(vWasm.name).toBe('v-wasm'); + expect(vWasm.title).toBe('V (Wasm)'); + expect(vWasm.compiler).toMatchObject({ + scriptType: 'text/v-wasm', + compiledCodeLanguage: 'v', + liveReload: true, + }); + }); + + test('accepts the common V extensions', () => { + expect(vWasm.extensions).toEqual( + expect.arrayContaining(['v', 'vlang', 'v-wasm', 'wasm.v', 'vsh']), + ); + }); + + test('warns that the first run is a large download', () => { + // The V compiler and the Clang toolchain together are tens of megabytes. + expect(vWasm.largeDownload).toBe(true); + }); + + test('uses a code editor with V support', () => { + expect(vWasm.editor).toBe('script'); + // There is no dedicated V mode, so the Go mode stands in for the editors that lack one. + expect(vWasm.editorSupport?.monaco?.language).toBe('go'); + // Prism does ship a V grammar, so CodeJar uses the real one. + expect(vWasm.editorSupport?.codejar?.language).toBe('v'); + }); +}); diff --git a/src/livecodes/languages/v-wasm/index.ts b/src/livecodes/languages/v-wasm/index.ts new file mode 100644 index 0000000000..33584b8e7c --- /dev/null +++ b/src/livecodes/languages/v-wasm/index.ts @@ -0,0 +1 @@ +export * from './lang-v-wasm'; diff --git a/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts b/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts new file mode 100644 index 0000000000..20e5072a87 --- /dev/null +++ b/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts @@ -0,0 +1,329 @@ +/* eslint-disable no-console */ +import { createWorkerFromContent, getErrorMessage } from '../../utils/utils'; +import { vWasmBaseUrl, vWasmClangBaseUrl } from '../../vendors'; + +// One runner for one language. The compiler is @live-codes/v-wasm, loaded from its CDN build: the +// IIFE defines `self.vWasm`, which the worker reaches with importScripts. It compiles V to C and +// hands that to the same Clang toolchain the C/C++ languages use, then runs the linked module in +// place, because a browser has no linker subprocess to hand a binary to. +// +// The V compiler is a classic Emscripten bundle: it decides it is in a worker by finding +// `importScripts`, so it has to run in a classic worker (the one createWorkerFromContent builds). +const SCRIPT_TYPE = 'text/v-wasm'; + +interface WorkerRunResult { + output: string; + errors: string[]; + exitCode: number | null; +} + +interface RunResult { + input: string; + output: string | null; + error: string | null; + exitCode: number | null; +} + +interface Runner { + ensureReady: () => Promise; + run: (code: string, input: string) => Promise; +} + +interface VWasmApi { + ready: boolean; + failed: boolean; + input: string; + output: string | null; + error: string | null; + exitCode: number | null; + loaded: Promise; + init: Promise | null; + runner: Runner | null; + run: (input?: string) => Promise; +} + +declare const window: Window & { + livecodes: Record; +}; + +const getWorkerSrc = () => ` +importScripts(${JSON.stringify(vWasmBaseUrl + 'dist/v-wasm.global.js')}); + +let compiler = null; + +const getCompiler = () => { + compiler = + compiler || + self.vWasm.createCompiler({ + baseUrl: ${JSON.stringify(vWasmBaseUrl + 'assets/v/')}, + // The Clang half is pinned by the package, so it comes from that exact version's assets. + clangBaseUrl: ${JSON.stringify(vWasmClangBaseUrl)}, + }); + return compiler; +}; + +addEventListener('message', async (event) => { + const { id, code, input } = event.data; + try { + const result = await (await getCompiler()).run(code, input); + postMessage({ + id, + result: { + output: result.output, + // The package reports V's, clang's and the linker's diagnostics as a list of lines, empty + // when the program built and ran. + errors: result.errors || [], + exitCode: result.exitCode, + }, + }); + } catch (error) { + postMessage({ id, error: String((error && error.message) || error) }); + } +}); + +getCompiler().then( + () => postMessage({ type: 'ready' }), + (error) => postMessage({ type: 'error', message: String((error && error.message) || error) }), +); +`; + +// A single compile + run is normally quick; a long timeout guards against hangs (e.g. an infinite +// loop in the user program) by killing and respawning the worker. +const RUN_TIMEOUT_MS = 120000; + +interface Pending { + resolve: (result: WorkerRunResult) => void; + reject: (error: Error) => void; + timer: ReturnType; +} + +const createRunner = (): Runner => { + let worker: Worker | null = null; + let ready: Promise | null = null; + let settleReady: ((error?: Error) => void) | null = null; + let pending: Record = {}; + let nextId = 1; + + const failAll = (error: Error) => { + for (const id of Object.keys(pending)) { + const request = pending[Number(id)]; + clearTimeout(request.timer); + request.reject(error); + } + pending = {}; + }; + + const teardown = (error: Error) => { + worker?.terminate(); + worker = null; + ready = null; + settleReady?.(error); + settleReady = null; + failAll(error); + }; + + const onMessage = (event: MessageEvent) => { + const message = event.data ?? {}; + if (message.type === 'ready') { + settleReady?.(); + settleReady = null; + return; + } + if (message.type === 'error') { + // The runtime failed to load (network, or the browser is unsupported). + teardown(new Error(message.message)); + return; + } + const request = pending[message.id]; + if (!request) return; + delete pending[message.id]; + clearTimeout(request.timer); + if (message.error != null) { + request.reject(new Error(message.error)); + } else { + request.resolve(message.result); + } + }; + + const spawn = () => { + ready = new Promise((resolve, reject) => { + settleReady = (error?: Error) => (error ? reject(error) : resolve()); + }); + worker = createWorkerFromContent(getWorkerSrc()); + worker.onmessage = onMessage; + worker.onerror = (event) => teardown(new Error(`The V worker crashed: ${event.message}`)); + }; + + /** Spawn the worker if needed and resolve once the compiler has loaded. */ + const ensureReady = async () => { + if (!ready) spawn(); + await ready; + }; + + const run = (code: string, input: string) => + ensureReady().then( + () => + new Promise((resolve, reject) => { + // `ensureReady` can resolve after the worker has been torn down (crash or timeout), so + // the worker may be gone by the time the request is registered. + if (!worker) { + reject(new Error('The V worker is not available')); + return; + } + const id = nextId++; + const timer = setTimeout(() => { + // The worker hung — kill it and reject, so the next run respawns a fresh worker. + teardown(new Error('The V compiler timed out')); + }, RUN_TIMEOUT_MS); + pending[id] = { resolve, reject, timer }; + worker.postMessage({ id, code, input }); + }), + ); + + return { ensureReady, run }; +}; + +/** + * Reads the program out of the result page. + * + * The result page writes the code inline as the `innerHTML` of a + * ` +`.trimStart(), + }, + style: { + language: 'css', + content: ` +.container, +.container button { + text-align: center; + font: 1em sans-serif; +} +.logo { + width: 150px; +} +`.trimStart(), + }, + script: { + language: 'v-wasm', + content: ` +import os + +fn main() { + count := os.input('').int() + println('V') + println(count + 1) +} +`.trimStart(), + }, +}; diff --git a/src/livecodes/vendors.ts b/src/livecodes/vendors.ts index 8fd81b246f..d39e0f7865 100644 --- a/src/livecodes/vendors.ts +++ b/src/livecodes/vendors.ts @@ -531,6 +531,12 @@ export const unicornJsX86Url = /* @__PURE__ */ getUrl( export const uniterUrl = /* @__PURE__ */ getUrl('uniter@2.18.0/dist/uniter.js'); +// `@live-codes/v-wasm` bundles this exact `@live-codes/clang-wasm` version for the Clang half of its +// pipeline, so its assets are fetched from that version rather than the one the C/C++ languages use. +export const vWasmClangBaseUrl = /* @__PURE__ */ getUrl('@live-codes/clang-wasm@0.3.0/assets/'); + +export const vWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/v-wasm@0.1.0/'); + export const vbWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/vb-wasm@0.1.1/'); export const vegaCdnUrl = /* @__PURE__ */ getUrl('vega@5.25.0/build/vega.js'); diff --git a/src/sdk/models.ts b/src/sdk/models.ts index b746207a07..67efd59750 100644 --- a/src/sdk/models.ts +++ b/src/sdk/models.ts @@ -185,6 +185,11 @@ export type Language = | 'ftn' | 'fortran-wasm' | 'lfortran' + | 'v' + | 'vlang' + | 'v-wasm' + | 'wasm.v' + | 'vsh' | 'cpp-wasm' | 'cppwasm' | 'wasm.cpp' @@ -484,6 +489,7 @@ export type TemplateName = | 'cpp-wasm' | 'objc-wasm' | 'fortran-wasm' + | 'v-wasm' | 'java' | 'csharp-wasm' | 'blazor-wasm' @@ -911,6 +917,7 @@ export type CustomSettings = Partial< | 'text/minizinc' | 'text/go-wasm' | 'text/zig-wasm' + | 'text/v-wasm' | 'application/json' | 'application/lua' | 'text/fennel' diff --git a/storybook/_stories/EmbedOptions/template.ts b/storybook/_stories/EmbedOptions/template.ts index d9ff83d65c..a64a7f0c72 100644 --- a/storybook/_stories/EmbedOptions/template.ts +++ b/storybook/_stories/EmbedOptions/template.ts @@ -51,6 +51,7 @@ const storyDef: StoryDef = { CWasm: { props: { template: 'c-wasm' } }, CppWasm: { props: { template: 'cpp-wasm' } }, ObjcWasm: { props: { template: 'objc-wasm' } }, + VWasm: { props: { template: 'v-wasm' } }, RustWasm: { props: { template: 'rust-wasm' } }, SwiftWasm: { props: { template: 'swift-wasm' } }, ZigWasm: { props: { template: 'zig-wasm' } }, diff --git a/storybook/preact/stories/EmbedOptions/template.stories.ts b/storybook/preact/stories/EmbedOptions/template.stories.ts index a9e2264a5f..c775c91586 100644 --- a/storybook/preact/stories/EmbedOptions/template.stories.ts +++ b/storybook/preact/stories/EmbedOptions/template.stories.ts @@ -66,6 +66,7 @@ export const CWasm = livecodesStory({ template: 'c-wasm' }); export const CppWasm = livecodesStory({ template: 'cpp-wasm' }); export const ObjcWasm = livecodesStory({ template: 'objc-wasm' }); export const FortranWasm = livecodesStory({ template: 'fortran-wasm' }); +export const VWasm = livecodesStory({ template: 'v-wasm' }); export const RustWasm = livecodesStory({ template: 'rust-wasm' }); export const SwiftWasm = livecodesStory({ template: 'swift-wasm' }); export const ZigWasm = livecodesStory({ template: 'zig-wasm' }); diff --git a/storybook/react/stories/EmbedOptions/template.stories.ts b/storybook/react/stories/EmbedOptions/template.stories.ts index a9e2264a5f..c775c91586 100644 --- a/storybook/react/stories/EmbedOptions/template.stories.ts +++ b/storybook/react/stories/EmbedOptions/template.stories.ts @@ -66,6 +66,7 @@ export const CWasm = livecodesStory({ template: 'c-wasm' }); export const CppWasm = livecodesStory({ template: 'cpp-wasm' }); export const ObjcWasm = livecodesStory({ template: 'objc-wasm' }); export const FortranWasm = livecodesStory({ template: 'fortran-wasm' }); +export const VWasm = livecodesStory({ template: 'v-wasm' }); export const RustWasm = livecodesStory({ template: 'rust-wasm' }); export const SwiftWasm = livecodesStory({ template: 'swift-wasm' }); export const ZigWasm = livecodesStory({ template: 'zig-wasm' }); diff --git a/storybook/solid/stories/EmbedOptions/template.stories.ts b/storybook/solid/stories/EmbedOptions/template.stories.ts index a9e2264a5f..c775c91586 100644 --- a/storybook/solid/stories/EmbedOptions/template.stories.ts +++ b/storybook/solid/stories/EmbedOptions/template.stories.ts @@ -66,6 +66,7 @@ export const CWasm = livecodesStory({ template: 'c-wasm' }); export const CppWasm = livecodesStory({ template: 'cpp-wasm' }); export const ObjcWasm = livecodesStory({ template: 'objc-wasm' }); export const FortranWasm = livecodesStory({ template: 'fortran-wasm' }); +export const VWasm = livecodesStory({ template: 'v-wasm' }); export const RustWasm = livecodesStory({ template: 'rust-wasm' }); export const SwiftWasm = livecodesStory({ template: 'swift-wasm' }); export const ZigWasm = livecodesStory({ template: 'zig-wasm' }); diff --git a/storybook/svelte/stories/EmbedOptions/template.stories.ts b/storybook/svelte/stories/EmbedOptions/template.stories.ts index a9e2264a5f..c775c91586 100644 --- a/storybook/svelte/stories/EmbedOptions/template.stories.ts +++ b/storybook/svelte/stories/EmbedOptions/template.stories.ts @@ -66,6 +66,7 @@ export const CWasm = livecodesStory({ template: 'c-wasm' }); export const CppWasm = livecodesStory({ template: 'cpp-wasm' }); export const ObjcWasm = livecodesStory({ template: 'objc-wasm' }); export const FortranWasm = livecodesStory({ template: 'fortran-wasm' }); +export const VWasm = livecodesStory({ template: 'v-wasm' }); export const RustWasm = livecodesStory({ template: 'rust-wasm' }); export const SwiftWasm = livecodesStory({ template: 'swift-wasm' }); export const ZigWasm = livecodesStory({ template: 'zig-wasm' }); diff --git a/storybook/vue/stories/EmbedOptions/template.stories.ts b/storybook/vue/stories/EmbedOptions/template.stories.ts index a9e2264a5f..c775c91586 100644 --- a/storybook/vue/stories/EmbedOptions/template.stories.ts +++ b/storybook/vue/stories/EmbedOptions/template.stories.ts @@ -66,6 +66,7 @@ export const CWasm = livecodesStory({ template: 'c-wasm' }); export const CppWasm = livecodesStory({ template: 'cpp-wasm' }); export const ObjcWasm = livecodesStory({ template: 'objc-wasm' }); export const FortranWasm = livecodesStory({ template: 'fortran-wasm' }); +export const VWasm = livecodesStory({ template: 'v-wasm' }); export const RustWasm = livecodesStory({ template: 'rust-wasm' }); export const SwiftWasm = livecodesStory({ template: 'swift-wasm' }); export const ZigWasm = livecodesStory({ template: 'zig-wasm' }); diff --git a/storybook/web-components/stories/EmbedOptions/template.stories.ts b/storybook/web-components/stories/EmbedOptions/template.stories.ts index a9e2264a5f..c775c91586 100644 --- a/storybook/web-components/stories/EmbedOptions/template.stories.ts +++ b/storybook/web-components/stories/EmbedOptions/template.stories.ts @@ -66,6 +66,7 @@ export const CWasm = livecodesStory({ template: 'c-wasm' }); export const CppWasm = livecodesStory({ template: 'cpp-wasm' }); export const ObjcWasm = livecodesStory({ template: 'objc-wasm' }); export const FortranWasm = livecodesStory({ template: 'fortran-wasm' }); +export const VWasm = livecodesStory({ template: 'v-wasm' }); export const RustWasm = livecodesStory({ template: 'rust-wasm' }); export const SwiftWasm = livecodesStory({ template: 'swift-wasm' }); export const ZigWasm = livecodesStory({ template: 'zig-wasm' }); diff --git a/vendor-licenses.md b/vendor-licenses.md index 1df1547e4a..42277e8879 100644 --- a/vendor-licenses.md +++ b/vendor-licenses.md @@ -348,6 +348,10 @@ wacl: [BSD 3-Clause License](https://github.com/ecky-l/wacl/blob/9daacabb0102a99 LFortran (Wasm): [BSD 3-Clause License](https://github.com/lfortran/lfortran/blob/main/LICENSE) +@live-codes/v-wasm: [MIT License](https://github.com/live-codes/browser-v/blob/main/LICENSE) + +V (Wasm): [MIT License](https://github.com/vlang/v/blob/master/LICENSE) (the V compiler and its standard library, compiled to WebAssembly by @live-codes/v-wasm; the Clang toolchain it links through is covered by LLVM / LLD below) + LLVM / LLD (Wasm): [Apache License 2.0 with LLVM-exception](https://github.com/llvm/llvm-project/blob/main/LICENSE.TXT) @wasm-fmt/clang-format: [MIT License](https://github.com/wasm-fmt/clang-format/blob/6a84f2d980e5ff4145bd9178a49cd5146e48df78/LICENSE) From 7998504847701e93761bed1e7989826b6ced79e2 Mon Sep 17 00:00:00 2001 From: "pullfrog[bot]" <226033991+pullfrog[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 09:29:09 +0000 Subject: [PATCH 2/5] fix: deduplicate WASM worker runner to satisfy SonarCloud gate The quality gate failed on 16.6% duplication on new code. Extract the worker runner shared by the WASM languages into worker-runner.ts and use it from the v-wasm, fortran, clang, ada and assembly scripts, replacing the copy in each. --- .../ada-wasm/lang-ada-wasm-script.ts | 93 +------------ .../lang-assembly-wasm-script.ts | 97 +------------ .../clang-wasm/lang-clang-wasm-script.ts | 95 +------------ .../fortran-wasm/lang-fortran-wasm-script.ts | 110 +-------------- .../languages/v-wasm/lang-v-wasm-script.ts | 109 +-------------- src/livecodes/languages/worker-runner.ts | 130 ++++++++++++++++++ 6 files changed, 153 insertions(+), 481 deletions(-) create mode 100644 src/livecodes/languages/worker-runner.ts diff --git a/src/livecodes/languages/ada-wasm/lang-ada-wasm-script.ts b/src/livecodes/languages/ada-wasm/lang-ada-wasm-script.ts index 58d7190c31..0d814c1d16 100644 --- a/src/livecodes/languages/ada-wasm/lang-ada-wasm-script.ts +++ b/src/livecodes/languages/ada-wasm/lang-ada-wasm-script.ts @@ -1,6 +1,7 @@ /* eslint-disable no-console */ -import { createWorkerFromContent, getErrorMessage } from '../../utils/utils'; +import { getErrorMessage } from '../../utils/utils'; import { adaWasmBaseUrl } from '../../vendors'; +import { createWorkerRunner, type Runner } from '../worker-runner'; // Ada is compiled and run by HAC (@live-codes/ada-wasm), a single self-contained // WebAssembly bundle. It runs inside a dedicated worker so a non-terminating @@ -24,12 +25,6 @@ interface RunResult { exitCode: number | null; } -interface Runner { - ensureReady: () => Promise; - run: (code: string, input: string) => Promise; - destroy: () => void; -} - interface AdaWasmApi { ready: boolean; failed: boolean; @@ -39,7 +34,7 @@ interface AdaWasmApi { exitCode: number | null; loaded: Promise; init: Promise | null; - runner?: Runner; + runner?: Runner; run: (input?: string) => Promise; } @@ -103,83 +98,6 @@ getRuntime().then( ); `; -interface Pending { - resolve: (result: WorkerRunResult) => void; - reject: (error: Error) => void; -} - -const createRunner = (): Runner => { - let worker: Worker | null = null; - let ready: Promise | null = null; - let settleReady: ((error?: Error) => void) | null = null; - let pending: Record = {}; - let nextId = 1; - - const failAll = (error: Error) => { - for (const id of Object.keys(pending)) { - pending[Number(id)].reject(error); - } - pending = {}; - }; - - const teardown = (error?: Error) => { - worker?.terminate(); - worker = null; - ready = null; - settleReady?.(error); - settleReady = null; - if (error) failAll(error); - }; - - const onMessage = (event: MessageEvent) => { - const message = event.data ?? {}; - if (message.type === 'ready') { - settleReady?.(); - settleReady = null; - return; - } - if (message.type === 'error') { - // The runtime failed to load (network, or the browser is unsupported). - teardown(new Error(message.message)); - return; - } - const request = pending[message.id]; - if (!request) return; - delete pending[message.id]; - if (message.error != null) { - request.reject(new Error(message.error)); - } else { - request.resolve(message.result); - } - }; - - const spawn = () => { - ready = new Promise((resolve, reject) => { - settleReady = (error?: Error) => (error ? reject(error) : resolve()); - }); - worker = createWorkerFromContent(getWorkerSrc()); - worker.onmessage = onMessage; - worker.onerror = (event) => teardown(new Error(`The Ada worker crashed: ${event.message}`)); - }; - - const ensureReady = async () => { - if (!ready) spawn(); - await ready; - }; - - const run = (code: string, input: string) => - ensureReady().then( - () => - new Promise((resolve, reject) => { - const id = nextId++; - pending[id] = { resolve, reject }; - worker?.postMessage({ id, code, input }); - }), - ); - - return { ensureReady, run, destroy: () => teardown() }; -}; - const setResult = ( input: string, output: string | null, @@ -207,7 +125,7 @@ adaWasm.ready = false; adaWasm.failed = false; /** Start (once) loading the runtime in a worker, showing the loading indicator. */ -const ensureLoaded = (runner: Runner): Promise => { +const ensureLoaded = (runner: Runner): Promise => { let init = adaWasm.init; if (!init) { adaWasm.failed = false; @@ -258,7 +176,8 @@ adaWasm.run = async (input?: string) => { }); if (!code.trim()) return setResult(stdin, null, null, 0); - const runner = (adaWasm.runner = adaWasm.runner || createRunner()); + const runner = (adaWasm.runner = + adaWasm.runner || createWorkerRunner({ getWorkerSrc, label: 'Ada' })); try { await ensureLoaded(runner); diff --git a/src/livecodes/languages/assembly-wasm/lang-assembly-wasm-script.ts b/src/livecodes/languages/assembly-wasm/lang-assembly-wasm-script.ts index 0b1d063490..b2b031da86 100644 --- a/src/livecodes/languages/assembly-wasm/lang-assembly-wasm-script.ts +++ b/src/livecodes/languages/assembly-wasm/lang-assembly-wasm-script.ts @@ -1,11 +1,12 @@ /* eslint-disable no-console */ -import { createWorkerFromContent, getErrorMessage } from '../../utils/utils'; +import { getErrorMessage } from '../../utils/utils'; import { assemblyWasmBaseUrl, keystoneJsUrl, keystoneJsWasmUrl, unicornJsX86Url, } from '../../vendors'; +import { createWorkerRunner, type Runner } from '../worker-runner'; // x86-64 assembly is assembled and executed by @live-codes/assembly-wasm, which loads // Keystone (the assembler) and Unicorn (the CPU emulator) as separate WebAssembly @@ -20,12 +21,6 @@ const SCRIPT_TYPE = 'text/assembly'; const BOOT_TIMEOUT_MS = 2 * 60_000; -interface WorkerRunResult { - output: string; - errors: string[]; - exitCode: number | null; -} - interface RunResult { input: string; output: string | null; @@ -33,12 +28,6 @@ interface RunResult { exitCode: number | null; } -interface Runner { - ensureReady: () => Promise; - run: (code: string, input: string) => Promise; - destroy: () => void; -} - interface AssemblyWasmApi { ready: boolean; failed: boolean; @@ -118,85 +107,6 @@ getCompiler().then( ); `; -interface Pending { - resolve: (result: WorkerRunResult) => void; - reject: (error: Error) => void; -} - -const createRunner = (): Runner => { - let worker: Worker | null = null; - let ready: Promise | null = null; - let settleReady: ((error?: Error) => void) | null = null; - let pending: Record = {}; - let nextId = 1; - - const failAll = (error: Error) => { - for (const id of Object.keys(pending)) { - pending[Number(id)].reject(error); - } - pending = {}; - }; - - const teardown = (error?: Error) => { - worker?.terminate(); - worker = null; - ready = null; - settleReady?.(error); - settleReady = null; - if (error) failAll(error); - }; - - const onMessage = (event: MessageEvent) => { - const message = event.data ?? {}; - if (message.type === 'ready') { - settleReady?.(); - settleReady = null; - return; - } - if (message.type === 'error') { - // The runtime failed to load (network, or the browser is unsupported). - teardown(new Error(message.message)); - return; - } - const request = pending[message.id]; - if (!request) return; - delete pending[message.id]; - if (message.error != null) { - request.reject(new Error(message.error)); - } else { - request.resolve(message.result); - } - }; - - const spawn = () => { - ready = new Promise((resolve, reject) => { - settleReady = (error?: Error) => (error ? reject(error) : resolve()); - }); - worker = createWorkerFromContent(getWorkerSrc()); - worker.onmessage = onMessage; - worker.onerror = (event) => - teardown(new Error(`The Assembly worker crashed: ${event.message}`)); - }; - - /** Spawn the worker if needed and resolve once the runtime has loaded. */ - const ensureReady = async () => { - if (!ready) spawn(); - await ready; - }; - - const run = (code: string, input: string) => - ensureReady().then( - () => - new Promise((resolve, reject) => { - const id = nextId++; - pending[id] = { resolve, reject }; - worker?.postMessage({ id, code, input }); - }), - ); - - return { ensureReady, run, destroy: () => teardown() }; -}; - const setResult = ( input: string, output: string | null, @@ -277,7 +187,8 @@ assemblyWasm.run = async (input?: string) => { // Park the runner on the persisted namespace so a live reload reuses the warm // worker instead of loading the runtimes again. - const runner = (assemblyWasm.runner = assemblyWasm.runner || createRunner()); + const runner = (assemblyWasm.runner = + assemblyWasm.runner || createWorkerRunner({ getWorkerSrc, label: 'Assembly' })); try { await ensureLoaded(runner); diff --git a/src/livecodes/languages/clang-wasm/lang-clang-wasm-script.ts b/src/livecodes/languages/clang-wasm/lang-clang-wasm-script.ts index 4b751753a9..6d53ab349b 100644 --- a/src/livecodes/languages/clang-wasm/lang-clang-wasm-script.ts +++ b/src/livecodes/languages/clang-wasm/lang-clang-wasm-script.ts @@ -1,6 +1,7 @@ /* eslint-disable no-console */ -import { createWorkerFromContent, getErrorMessage } from '../../utils/utils'; +import { getErrorMessage } from '../../utils/utils'; import { clangWasmBaseUrl } from '../../vendors'; +import { createWorkerRunner, type Runner } from '../worker-runner'; // One runner serves all four Clang languages. The language is read from the script tags the // compiler injected into the page, so only the script type differs between them. @@ -35,12 +36,6 @@ interface ClangWasmSettings { args?: string[]; } -interface WorkerRunResult { - output: string; - errors: string[]; - exitCode: number | null; -} - interface RunResult { input: string; output: string | null; @@ -48,11 +43,6 @@ interface RunResult { exitCode: number | null; } -interface Runner { - ensureReady: () => Promise; - run: (code: string, input: string, options?: ClangWasmSettings) => Promise; -} - interface ClangWasmApi { ready: boolean; input: string; @@ -107,84 +97,6 @@ getCompiler().then( ); `; -interface Pending { - resolve: (result: WorkerRunResult) => void; - reject: (error: Error) => void; -} - -const createRunner = (language: LanguageId): Runner => { - let worker: Worker | null = null; - let ready: Promise | null = null; - let settleReady: ((error?: Error) => void) | null = null; - let pending: Record = {}; - let nextId = 1; - - const failAll = (error: Error) => { - for (const id of Object.keys(pending)) { - pending[Number(id)].reject(error); - } - pending = {}; - }; - - const teardown = (error: Error) => { - worker?.terminate(); - worker = null; - ready = null; - settleReady?.(error); - settleReady = null; - failAll(error); - }; - - const onMessage = (event: MessageEvent) => { - const message = event.data ?? {}; - if (message.type === 'ready') { - settleReady?.(); - settleReady = null; - return; - } - if (message.type === 'error') { - // The runtime failed to load (network, or the browser is unsupported). - teardown(new Error(message.message)); - return; - } - const request = pending[message.id]; - if (!request) return; - delete pending[message.id]; - if (message.error != null) { - request.reject(new Error(message.error)); - } else { - request.resolve(message.result); - } - }; - - const spawn = () => { - ready = new Promise((resolve, reject) => { - settleReady = (error?: Error) => (error ? reject(error) : resolve()); - }); - worker = createWorkerFromContent(getWorkerSrc(language)); - worker.onmessage = onMessage; - worker.onerror = (event) => teardown(new Error(`The Clang worker crashed: ${event.message}`)); - }; - - /** Spawn the worker if needed and resolve once the compiler has loaded. */ - const ensureReady = async () => { - if (!ready) spawn(); - await ready; - }; - - const run = (code: string, input: string, options?: ClangWasmSettings) => - ensureReady().then( - () => - new Promise((resolve, reject) => { - const id = nextId++; - pending[id] = { resolve, reject }; - worker?.postMessage({ id, code, input, options }); - }), - ); - - return { ensureReady, run }; -}; - const getLanguage = (): LanguageId | null => { for (const scriptType of SCRIPT_TYPES) { if (document.querySelector(`script[type="${scriptType}"]`)) { @@ -282,7 +194,8 @@ clangWasm.run = async (input?: string) => { if (!code.trim()) return setResult(stdin, null, null, null); const runner = (clangWasm.runners[language] = - clangWasm.runners[language] || createRunner(language)); + clangWasm.runners[language] || + createWorkerRunner({ getWorkerSrc: () => getWorkerSrc(language), label: 'Clang' })); try { await ensureLoaded(runner); diff --git a/src/livecodes/languages/fortran-wasm/lang-fortran-wasm-script.ts b/src/livecodes/languages/fortran-wasm/lang-fortran-wasm-script.ts index 4b7f702c63..5ba0b8e61c 100644 --- a/src/livecodes/languages/fortran-wasm/lang-fortran-wasm-script.ts +++ b/src/livecodes/languages/fortran-wasm/lang-fortran-wasm-script.ts @@ -1,6 +1,7 @@ /* eslint-disable no-console */ -import { createWorkerFromContent, getErrorMessage } from '../../utils/utils'; +import { getErrorMessage } from '../../utils/utils'; import { fortranWasmBaseUrl } from '../../vendors'; +import { createWorkerRunner, type Runner } from '../worker-runner'; // One runner for one language. The compiler itself is @live-codes/lfortran-wasm, loaded from its // CDN build: the IIFE defines `self.lfortranWasm`, which the worker reaches with importScripts. @@ -8,12 +9,6 @@ import { fortranWasmBaseUrl } from '../../vendors'; // subprocess to hand a binary to. const SCRIPT_TYPE = 'text/fortran-wasm'; -interface WorkerRunResult { - output: string; - errors: string[]; - exitCode: number | null; -} - interface RunResult { input: string; output: string | null; @@ -21,11 +16,6 @@ interface RunResult { exitCode: number | null; } -interface Runner { - ensureReady: () => Promise; - run: (code: string, input: string) => Promise; -} - interface FortranWasmApi { ready: boolean; input: string; @@ -84,98 +74,6 @@ getCompiler().then( // loop in the user program) by killing and respawning the worker. const RUN_TIMEOUT_MS = 120000; -interface Pending { - resolve: (result: WorkerRunResult) => void; - reject: (error: Error) => void; - timer: ReturnType; -} - -const createRunner = (): Runner => { - let worker: Worker | null = null; - let ready: Promise | null = null; - let settleReady: ((error?: Error) => void) | null = null; - let pending: Record = {}; - let nextId = 1; - - const failAll = (error: Error) => { - for (const id of Object.keys(pending)) { - const request = pending[Number(id)]; - clearTimeout(request.timer); - request.reject(error); - } - pending = {}; - }; - - const teardown = (error: Error) => { - worker?.terminate(); - worker = null; - ready = null; - settleReady?.(error); - settleReady = null; - failAll(error); - }; - - const onMessage = (event: MessageEvent) => { - const message = event.data ?? {}; - if (message.type === 'ready') { - settleReady?.(); - settleReady = null; - return; - } - if (message.type === 'error') { - // The runtime failed to load (network, or the browser is unsupported). - teardown(new Error(message.message)); - return; - } - const request = pending[message.id]; - if (!request) return; - delete pending[message.id]; - clearTimeout(request.timer); - if (message.error != null) { - request.reject(new Error(message.error)); - } else { - request.resolve(message.result); - } - }; - - const spawn = () => { - ready = new Promise((resolve, reject) => { - settleReady = (error?: Error) => (error ? reject(error) : resolve()); - }); - worker = createWorkerFromContent(getWorkerSrc()); - worker.onmessage = onMessage; - worker.onerror = (event) => teardown(new Error(`The Fortran worker crashed: ${event.message}`)); - }; - - /** Spawn the worker if needed and resolve once the compiler has loaded. */ - const ensureReady = async () => { - if (!ready) spawn(); - await ready; - }; - - const run = (code: string, input: string) => - ensureReady().then( - () => - new Promise((resolve, reject) => { - // `ensureReady` can resolve after the worker has been torn down (crash or timeout), so - // the worker may be gone by the time the request is registered. - if (!worker) { - reject(new Error('The Fortran worker is not available')); - return; - } - const id = nextId++; - const timer = setTimeout(() => { - // The worker hung — kill it and reject, so the next run respawns a fresh worker. - teardown(new Error('The Fortran compiler timed out')); - }, RUN_TIMEOUT_MS); - pending[id] = { resolve, reject, timer }; - worker.postMessage({ id, code, input }); - }), - ); - - return { ensureReady, run }; -}; - /** * Reads the program out of the result page. * @@ -264,7 +162,9 @@ fortranWasm.run = async (input?: string) => { return setResult(stdin, null, null, null); } - const runner = (fortranWasm.runner = fortranWasm.runner || createRunner()); + const runner = (fortranWasm.runner = + fortranWasm.runner || + createWorkerRunner({ getWorkerSrc, label: 'Fortran', timeoutMs: RUN_TIMEOUT_MS })); try { await ensureLoaded(runner); diff --git a/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts b/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts index 20e5072a87..3a4f0c4b16 100644 --- a/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts +++ b/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts @@ -1,6 +1,7 @@ /* eslint-disable no-console */ -import { createWorkerFromContent, getErrorMessage } from '../../utils/utils'; +import { getErrorMessage } from '../../utils/utils'; import { vWasmBaseUrl, vWasmClangBaseUrl } from '../../vendors'; +import { createWorkerRunner, type Runner } from '../worker-runner'; // One runner for one language. The compiler is @live-codes/v-wasm, loaded from its CDN build: the // IIFE defines `self.vWasm`, which the worker reaches with importScripts. It compiles V to C and @@ -11,12 +12,6 @@ import { vWasmBaseUrl, vWasmClangBaseUrl } from '../../vendors'; // `importScripts`, so it has to run in a classic worker (the one createWorkerFromContent builds). const SCRIPT_TYPE = 'text/v-wasm'; -interface WorkerRunResult { - output: string; - errors: string[]; - exitCode: number | null; -} - interface RunResult { input: string; output: string | null; @@ -24,11 +19,6 @@ interface RunResult { exitCode: number | null; } -interface Runner { - ensureReady: () => Promise; - run: (code: string, input: string) => Promise; -} - interface VWasmApi { ready: boolean; failed: boolean; @@ -91,98 +81,6 @@ getCompiler().then( // loop in the user program) by killing and respawning the worker. const RUN_TIMEOUT_MS = 120000; -interface Pending { - resolve: (result: WorkerRunResult) => void; - reject: (error: Error) => void; - timer: ReturnType; -} - -const createRunner = (): Runner => { - let worker: Worker | null = null; - let ready: Promise | null = null; - let settleReady: ((error?: Error) => void) | null = null; - let pending: Record = {}; - let nextId = 1; - - const failAll = (error: Error) => { - for (const id of Object.keys(pending)) { - const request = pending[Number(id)]; - clearTimeout(request.timer); - request.reject(error); - } - pending = {}; - }; - - const teardown = (error: Error) => { - worker?.terminate(); - worker = null; - ready = null; - settleReady?.(error); - settleReady = null; - failAll(error); - }; - - const onMessage = (event: MessageEvent) => { - const message = event.data ?? {}; - if (message.type === 'ready') { - settleReady?.(); - settleReady = null; - return; - } - if (message.type === 'error') { - // The runtime failed to load (network, or the browser is unsupported). - teardown(new Error(message.message)); - return; - } - const request = pending[message.id]; - if (!request) return; - delete pending[message.id]; - clearTimeout(request.timer); - if (message.error != null) { - request.reject(new Error(message.error)); - } else { - request.resolve(message.result); - } - }; - - const spawn = () => { - ready = new Promise((resolve, reject) => { - settleReady = (error?: Error) => (error ? reject(error) : resolve()); - }); - worker = createWorkerFromContent(getWorkerSrc()); - worker.onmessage = onMessage; - worker.onerror = (event) => teardown(new Error(`The V worker crashed: ${event.message}`)); - }; - - /** Spawn the worker if needed and resolve once the compiler has loaded. */ - const ensureReady = async () => { - if (!ready) spawn(); - await ready; - }; - - const run = (code: string, input: string) => - ensureReady().then( - () => - new Promise((resolve, reject) => { - // `ensureReady` can resolve after the worker has been torn down (crash or timeout), so - // the worker may be gone by the time the request is registered. - if (!worker) { - reject(new Error('The V worker is not available')); - return; - } - const id = nextId++; - const timer = setTimeout(() => { - // The worker hung — kill it and reject, so the next run respawns a fresh worker. - teardown(new Error('The V compiler timed out')); - }, RUN_TIMEOUT_MS); - pending[id] = { resolve, reject, timer }; - worker.postMessage({ id, code, input }); - }), - ); - - return { ensureReady, run }; -}; - /** * Reads the program out of the result page. * @@ -297,7 +195,8 @@ vWasm.run = async (input?: string) => { return setResult(stdin, null, null, null); } - const runner = (vWasm.runner = vWasm.runner || createRunner()); + const runner = (vWasm.runner = + vWasm.runner || createWorkerRunner({ getWorkerSrc, label: 'V', timeoutMs: RUN_TIMEOUT_MS })); try { await ensureLoaded(runner); diff --git a/src/livecodes/languages/worker-runner.ts b/src/livecodes/languages/worker-runner.ts new file mode 100644 index 0000000000..bf77e7557f --- /dev/null +++ b/src/livecodes/languages/worker-runner.ts @@ -0,0 +1,130 @@ +import { createWorkerFromContent } from '../utils/utils'; + +/** The result a language worker reports for a single compile + run. */ +export interface WorkerRunResult { + output: string; + errors: string[]; + exitCode: number | null; +} + +export interface Runner { + ensureReady: () => Promise; + run: (code: string, input: string, options?: unknown) => Promise; + destroy: () => void; +} + +export interface WorkerRunnerOptions { + /** + * The classic worker that loads the runtime. It must post `{ type: 'ready' | 'error' }` when the + * runtime has loaded (or failed to), and a run result keyed by the request id otherwise. + */ + getWorkerSrc: () => string; + /** Shown in crash, timeout and unavailability messages, e.g. `Fortran`. */ + label: string; + /** Kill the worker when a single run does not settle within this many milliseconds. */ + timeoutMs?: number; +} + +interface Pending { + resolve: (result: TResult) => void; + reject: (error: Error) => void; + timer?: ReturnType; +} + +/** + * A serialized worker runner shared by the WASM languages. It keeps one worker for the compiler's + * lifetime, tears it down on crashes/timeouts and respawns it on the next run. + */ +export const createWorkerRunner = ({ + getWorkerSrc, + label, + timeoutMs, +}: WorkerRunnerOptions): Runner => { + let worker: Worker | null = null; + let ready: Promise | null = null; + let settleReady: ((error?: Error) => void) | null = null; + let pending: Record> = {}; + let nextId = 1; + + const failAll = (error: Error) => { + for (const id of Object.keys(pending)) { + const request = pending[Number(id)]; + if (request.timer) clearTimeout(request.timer); + request.reject(error); + } + pending = {}; + }; + + const teardown = (error?: Error) => { + worker?.terminate(); + worker = null; + ready = null; + settleReady?.(error); + settleReady = null; + if (error) failAll(error); + }; + + const onMessage = (event: MessageEvent) => { + const message = event.data ?? {}; + if (message.type === 'ready') { + settleReady?.(); + settleReady = null; + return; + } + if (message.type === 'error') { + // The runtime failed to load (network, or the browser is unsupported). + teardown(new Error(message.message)); + return; + } + const request = pending[message.id]; + if (!request) return; + delete pending[message.id]; + if (request.timer) clearTimeout(request.timer); + if (message.error != null) { + request.reject(new Error(message.error)); + } else { + request.resolve(message.result); + } + }; + + const spawn = () => { + ready = new Promise((resolve, reject) => { + settleReady = (error?: Error) => (error ? reject(error) : resolve()); + }); + worker = createWorkerFromContent(getWorkerSrc()); + worker.onmessage = onMessage; + worker.onerror = (event) => + teardown(new Error(`The ${label} worker crashed: ${event.message}`)); + }; + + /** Spawn the worker if needed and resolve once the runtime has loaded. */ + const ensureReady = async () => { + if (!ready) spawn(); + await ready; + }; + + const run = (code: string, input: string, options?: unknown) => + ensureReady().then( + () => + new Promise((resolve, reject) => { + // `ensureReady` can resolve after the worker has been torn down (crash or timeout), so + // the worker may be gone by the time the request is registered. + if (!worker) { + reject(new Error(`The ${label} worker is not available`)); + return; + } + const id = nextId++; + const request: Pending = { resolve, reject }; + if (timeoutMs != null) { + request.timer = setTimeout(() => { + // The worker hung, so kill it and reject; the next run respawns a fresh worker. + teardown(new Error(`The ${label} compiler timed out`)); + }, timeoutMs); + } + pending[id] = request; + worker.postMessage({ id, code, input, options }); + }), + ); + + return { ensureReady, run, destroy: () => teardown() }; +}; From 9d946106cfc3d3a042bf986008d12d72355058eb Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Tue, 6 Oct 2026 20:06:50 +0300 Subject: [PATCH 3/5] use same clang-wasm version across languages --- .../languages/cobol-wasm/lang-cobol-wasm-script.ts | 4 ++-- src/livecodes/languages/v-wasm/lang-v-wasm-script.ts | 4 ++-- src/livecodes/vendors.ts | 10 +--------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/livecodes/languages/cobol-wasm/lang-cobol-wasm-script.ts b/src/livecodes/languages/cobol-wasm/lang-cobol-wasm-script.ts index 720c6d53e7..4ad06e2c99 100644 --- a/src/livecodes/languages/cobol-wasm/lang-cobol-wasm-script.ts +++ b/src/livecodes/languages/cobol-wasm/lang-cobol-wasm-script.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ import { getErrorMessage } from '../../utils/utils'; -import { cobolWasmBaseUrl, cobolWasmClangBaseUrl } from '../../vendors'; +import { clangWasmBaseUrl, cobolWasmBaseUrl } from '../../vendors'; import { createWorkerRunner, type Runner } from '../worker-runner'; // One runner for one language. GnuCOBOL is the real compiler (@live-codes/cobol-wasm), loaded from @@ -54,7 +54,7 @@ const getCompiler = () => { self.cobolWasm.createCompiler({ baseUrl: ${JSON.stringify(cobolWasmBaseUrl + 'assets/')}, // The Clang half is pinned by the package, so it comes from that exact version's assets. - clangBaseUrl: ${JSON.stringify(cobolWasmClangBaseUrl)}, + clangBaseUrl: ${JSON.stringify(clangWasmBaseUrl + 'assets/')}, }); return compiler; }; diff --git a/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts b/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts index 3a4f0c4b16..7da023dedb 100644 --- a/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts +++ b/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ import { getErrorMessage } from '../../utils/utils'; -import { vWasmBaseUrl, vWasmClangBaseUrl } from '../../vendors'; +import { clangWasmBaseUrl, vWasmBaseUrl } from '../../vendors'; import { createWorkerRunner, type Runner } from '../worker-runner'; // One runner for one language. The compiler is @live-codes/v-wasm, loaded from its CDN build: the @@ -47,7 +47,7 @@ const getCompiler = () => { self.vWasm.createCompiler({ baseUrl: ${JSON.stringify(vWasmBaseUrl + 'assets/v/')}, // The Clang half is pinned by the package, so it comes from that exact version's assets. - clangBaseUrl: ${JSON.stringify(vWasmClangBaseUrl)}, + clangBaseUrl: ${JSON.stringify(clangWasmBaseUrl + 'assets/')}, }); return compiler; }; diff --git a/src/livecodes/vendors.ts b/src/livecodes/vendors.ts index 7500a37d57..b6da5f97ce 100644 --- a/src/livecodes/vendors.ts +++ b/src/livecodes/vendors.ts @@ -62,7 +62,7 @@ export const cherryCljsBaseUrl = /* @__PURE__ */ getUrl('cherry-cljs@0.6.38/'); export const cjs2esUrl = /* @__PURE__ */ getUrl('cjs2es@1.1.1/dist/cjs2es.browser.js'); -export const clangWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/clang-wasm@0.2.0/'); +export const clangWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/clang-wasm@0.3.0/'); export const clioBaseUrl = /* @__PURE__ */ getUrl( '@live-codes/clio-browser-compiler@0.0.3/public/build/', @@ -104,10 +104,6 @@ export const cm6ThemeSolarizedDarkUrl = /* @__PURE__ */ getUrl( export const cobolWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/cobol-wasm@0.1.0/'); -// `@live-codes/cobol-wasm` pins this exact `@live-codes/clang-wasm` version for the Clang half of its -// toolchain, and the runtime re-derives those assets' digests, so the two must stay in step. -export const cobolWasmClangBaseUrl = /* @__PURE__ */ getUrl('@live-codes/clang-wasm@0.3.0/assets/'); - export const codeiumProviderUrl = /* @__PURE__ */ getUrl( '@live-codes/monaco-codeium-provider@0.2.2/dist/index.js', ); @@ -537,10 +533,6 @@ export const unicornJsX86Url = /* @__PURE__ */ getUrl( export const uniterUrl = /* @__PURE__ */ getUrl('uniter@2.18.0/dist/uniter.js'); -// `@live-codes/v-wasm` bundles this exact `@live-codes/clang-wasm` version for the Clang half of its -// pipeline, so its assets are fetched from that version rather than the one the C/C++ languages use. -export const vWasmClangBaseUrl = /* @__PURE__ */ getUrl('@live-codes/clang-wasm@0.3.0/assets/'); - export const vWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/v-wasm@0.1.0/'); export const vbWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/vb-wasm@0.1.1/'); From d1dd76b6df877f85a3af9a7eff67073191f531f6 Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Tue, 6 Oct 2026 20:08:56 +0300 Subject: [PATCH 4/5] add monaco V language support --- src/livecodes/languages/v-wasm/lang-v-wasm.ts | 6 ++---- src/livecodes/vendors.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/livecodes/languages/v-wasm/lang-v-wasm.ts b/src/livecodes/languages/v-wasm/lang-v-wasm.ts index bd4cf807a2..3622307e9a 100644 --- a/src/livecodes/languages/v-wasm/lang-v-wasm.ts +++ b/src/livecodes/languages/v-wasm/lang-v-wasm.ts @@ -14,11 +14,9 @@ export const vWasm: LanguageSpecs = { extensions: ['v', 'vlang', 'v-wasm', 'wasm.v', 'vsh'], editor: 'script', editorSupport: { - // There is no dedicated V mode in either editor, so the Go mode stands in: V's syntax is - // deliberately close to Go's. Prism does ship a real V grammar, so CodeJar uses that. monaco: { - languageSupport: monacoLanguagesBaseUrl + 'go.js', - language: 'go', + languageSupport: monacoLanguagesBaseUrl + 'v.js', + language: 'v', }, codemirror: { languageSupport: async () => (await import(codeMirrorBaseUrl + 'codemirror-lang-go.js')).go(), diff --git a/src/livecodes/vendors.ts b/src/livecodes/vendors.ts index b6da5f97ce..0d66238c54 100644 --- a/src/livecodes/vendors.ts +++ b/src/livecodes/vendors.ts @@ -360,7 +360,7 @@ export const monacoBaseUrl = /* @__PURE__ */ getUrl('@live-codes/monaco-editor@0 export const monacoEmacsUrl = /* @__PURE__ */ getUrl('monaco-emacs@0.3.0/dist/monaco-emacs.js'); export const monacoLanguagesBaseUrl = /* @__PURE__ */ getUrl( - '@live-codes/monaco-languages@0.3.10/dist/', + '@live-codes/monaco-languages@0.3.11/dist/', ); export const monacoThemesBaseUrl = /* @__PURE__ */ getUrl('monaco-themes@0.4.4/themes/'); From 565bb1fbebb6530aaad012b96217bc9382001868 Mon Sep 17 00:00:00 2001 From: "pullfrog[bot]" <226033991+pullfrog[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 17:23:19 +0000 Subject: [PATCH 5/5] fix: deduplicate WASM runtime boilerplate and correct V Monaco spec Extract getParentOrigin, createLoadingReporter and runCompiler into a shared wasm-runtime module and reuse them across the worker-runner WASM scripts (v-wasm, cobol, fortran, assembly, clang, fanak, haskell). This removes the duplicated blocks that pushed the SonarCloud new-code duplication above the 3% gate. Also update the v-wasm spec to expect the Monaco 'v' language, which now ships in @live-codes/monaco-languages@0.3.11, replacing the stale 'go' expectation. --- .../lang-assembly-wasm-script.ts | 21 +----- .../clang-wasm/lang-clang-wasm-script.ts | 20 +----- .../cobol-wasm/lang-cobol-wasm-script.ts | 41 ++---------- .../languages/fanak/lang-fanak-script.ts | 18 +---- .../fortran-wasm/lang-fortran-wasm-script.ts | 20 +----- .../haskell-wasm/lang-haskell-wasm-script.ts | 15 +---- .../v-wasm/__tests__/lang-v-wasm.spec.ts | 4 +- .../languages/v-wasm/lang-v-wasm-script.ts | 41 ++---------- src/livecodes/languages/wasm-runtime.ts | 67 +++++++++++++++++++ 9 files changed, 88 insertions(+), 159 deletions(-) create mode 100644 src/livecodes/languages/wasm-runtime.ts diff --git a/src/livecodes/languages/assembly-wasm/lang-assembly-wasm-script.ts b/src/livecodes/languages/assembly-wasm/lang-assembly-wasm-script.ts index b2b031da86..a01b634c8c 100644 --- a/src/livecodes/languages/assembly-wasm/lang-assembly-wasm-script.ts +++ b/src/livecodes/languages/assembly-wasm/lang-assembly-wasm-script.ts @@ -1,11 +1,11 @@ /* eslint-disable no-console */ -import { getErrorMessage } from '../../utils/utils'; import { assemblyWasmBaseUrl, keystoneJsUrl, keystoneJsWasmUrl, unicornJsX86Url, } from '../../vendors'; +import { runCompiler } from '../wasm-runtime'; import { createWorkerRunner, type Runner } from '../worker-runner'; // x86-64 assembly is assembled and executed by @live-codes/assembly-wasm, which loads @@ -190,24 +190,7 @@ assemblyWasm.run = async (input?: string) => { const runner = (assemblyWasm.runner = assemblyWasm.runner || createWorkerRunner({ getWorkerSrc, label: 'Assembly' })); - try { - await ensureLoaded(runner); - } catch (error) { - return setResult(stdin, null, `Error: ${getErrorMessage(error)}`, 1); - } - - try { - const result = await runner.run(code, stdin); - // `errors` holds the assembler's diagnostics and any runtime fault, and is empty - // when the program assembled and exited cleanly. - const errors = (result.errors || []).filter(Boolean); - if (errors.length) { - return setResult(stdin, null, errors.join('\n'), result.exitCode ?? 1); - } - return setResult(stdin, result.output ?? '', null, result.exitCode ?? 0); - } catch (error) { - return setResult(stdin, null, `Error: ${getErrorMessage(error)}`, 1); - } + return runCompiler(runner, ensureLoaded, code, stdin, setResult); }; // Alias, so both `livecodes.assemblyWasm` and `livecodes.asm` are available in markup. diff --git a/src/livecodes/languages/clang-wasm/lang-clang-wasm-script.ts b/src/livecodes/languages/clang-wasm/lang-clang-wasm-script.ts index 6d53ab349b..47177e7103 100644 --- a/src/livecodes/languages/clang-wasm/lang-clang-wasm-script.ts +++ b/src/livecodes/languages/clang-wasm/lang-clang-wasm-script.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ -import { getErrorMessage } from '../../utils/utils'; import { clangWasmBaseUrl } from '../../vendors'; +import { runCompiler } from '../wasm-runtime'; import { createWorkerRunner, type Runner } from '../worker-runner'; // One runner serves all four Clang languages. The language is read from the script tags the @@ -197,23 +197,7 @@ clangWasm.run = async (input?: string) => { clangWasm.runners[language] || createWorkerRunner({ getWorkerSrc: () => getWorkerSrc(language), label: 'Clang' })); - try { - await ensureLoaded(runner); - } catch (error) { - return setResult(stdin, null, `Error: ${getErrorMessage(error)}`, 1); - } - - try { - const result = await runner.run(code, stdin, getSettings(language)); - // `errors` holds the compiler's diagnostics and is empty when the program compiled. - const errors = (result.errors || []).filter(Boolean); - if (errors.length) { - return setResult(stdin, null, errors.join('\n'), result.exitCode ?? 1); - } - return setResult(stdin, result.output ?? '', null, result.exitCode ?? 0); - } catch (error) { - return setResult(stdin, null, `Error: ${getErrorMessage(error)}`, 1); - } + return runCompiler(runner, ensureLoaded, code, stdin, setResult, getSettings(language)); }; // One alias per language, plus `cpp` for playgrounds written before the languages were split. diff --git a/src/livecodes/languages/cobol-wasm/lang-cobol-wasm-script.ts b/src/livecodes/languages/cobol-wasm/lang-cobol-wasm-script.ts index 4ad06e2c99..549284e28a 100644 --- a/src/livecodes/languages/cobol-wasm/lang-cobol-wasm-script.ts +++ b/src/livecodes/languages/cobol-wasm/lang-cobol-wasm-script.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ -import { getErrorMessage } from '../../utils/utils'; import { clangWasmBaseUrl, cobolWasmBaseUrl } from '../../vendors'; +import { createLoadingReporter, runCompiler } from '../wasm-runtime'; import { createWorkerRunner, type Runner } from '../worker-runner'; // One runner for one language. GnuCOBOL is the real compiler (@live-codes/cobol-wasm), loaded from @@ -133,25 +133,8 @@ cobolWasm.failed = false; // ~25 MiB toolchain is not downloaded again, instead of spawning a new one. cobolWasm.runner ??= null; -// The result runs in an iframe; post status updates to the app origin. Mirrors -// the other WASM language scripts (`fanak`, `haskell-wasm`). -const parentOrigin = - window.parent === window - ? window.location.origin - : window.location.ancestorOrigins?.[0] || - (() => { - if (!document.referrer) return '*'; - try { - return new URL(document.referrer).origin; - } catch { - // Ignore malformed referrers and use the wildcard fallback below. - return '*'; - } - })(); - -const postLoading = (payload: boolean) => { - parent.postMessage({ type: 'loading', payload }, parentOrigin); // NOSONAR - fallback is safe with source/origin checks in the parent. -}; +// The result runs in an iframe; post status updates to the app origin. +const postLoading = createLoadingReporter(); /** Start (once) downloading the compiler, showing the loading indicator while it happens. */ const ensureLoaded = (runner: Runner): Promise => { @@ -206,23 +189,7 @@ cobolWasm.run = async (input?: string) => { cobolWasm.runner || createWorkerRunner({ getWorkerSrc, label: 'COBOL', timeoutMs: RUN_TIMEOUT_MS })); - try { - await ensureLoaded(runner); - } catch (error) { - return setResult(stdin, null, `Error: ${getErrorMessage(error)}`, 1); - } - - try { - const result = await runner.run(code, stdin, cobolWasm.settings); - // `errors` holds the compiler's diagnostics and is empty when the program ran. - const errors = (result.errors || []).filter(Boolean); - if (errors.length) { - return setResult(stdin, null, errors.join('\n'), result.exitCode ?? 1); - } - return setResult(stdin, result.output ?? '', null, result.exitCode ?? 0); - } catch (error) { - return setResult(stdin, null, `Error: ${getErrorMessage(error)}`, 1); - } + return runCompiler(runner, ensureLoaded, code, stdin, setResult, cobolWasm.settings); }; // Aliases so both `livecodes.cobol` and `livecodes.cobolWasm` are available. diff --git a/src/livecodes/languages/fanak/lang-fanak-script.ts b/src/livecodes/languages/fanak/lang-fanak-script.ts index 72816c348d..b6635133e1 100644 --- a/src/livecodes/languages/fanak/lang-fanak-script.ts +++ b/src/livecodes/languages/fanak/lang-fanak-script.ts @@ -1,5 +1,6 @@ import { getErrorMessage } from '../../utils'; import { fanakBaseUrl } from '../../vendors'; +import { getParentOrigin } from '../wasm-runtime'; declare const livecodes: any; @@ -24,21 +25,8 @@ const waitFor = async (condition: () => boolean | Promise, timeout = 60 return true; }; -// The result runs in an iframe; post status updates to the app origin. Mirrors -// the other WASM language scripts (`haskell-wasm`). -const parentOrigin = - window.parent === window - ? window.location.origin - : window.location.ancestorOrigins?.[0] || - (() => { - if (!document.referrer) return '*'; - try { - return new URL(document.referrer).origin; - } catch { - // Ignore malformed referrers and use the wildcard fallback below. - return '*'; - } - })(); +// The result runs in an iframe; post status updates to the app origin. +const parentOrigin = getParentOrigin(); let activeRuns = 0; const postLoading = (payload: boolean) => { diff --git a/src/livecodes/languages/fortran-wasm/lang-fortran-wasm-script.ts b/src/livecodes/languages/fortran-wasm/lang-fortran-wasm-script.ts index 5ba0b8e61c..2c237702aa 100644 --- a/src/livecodes/languages/fortran-wasm/lang-fortran-wasm-script.ts +++ b/src/livecodes/languages/fortran-wasm/lang-fortran-wasm-script.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ -import { getErrorMessage } from '../../utils/utils'; import { fortranWasmBaseUrl } from '../../vendors'; +import { runCompiler } from '../wasm-runtime'; import { createWorkerRunner, type Runner } from '../worker-runner'; // One runner for one language. The compiler itself is @live-codes/lfortran-wasm, loaded from its @@ -166,23 +166,7 @@ fortranWasm.run = async (input?: string) => { fortranWasm.runner || createWorkerRunner({ getWorkerSrc, label: 'Fortran', timeoutMs: RUN_TIMEOUT_MS })); - try { - await ensureLoaded(runner); - } catch (error) { - return setResult(stdin, null, `Error: ${getErrorMessage(error)}`, 1); - } - - try { - const result = await runner.run(code, stdin); - // `errors` holds the compiler's diagnostics and is empty when the program ran. - const errors = (result.errors || []).filter(Boolean); - if (errors.length) { - return setResult(stdin, null, errors.join('\n'), result.exitCode ?? 1); - } - return setResult(stdin, result.output ?? '', null, result.exitCode ?? 0); - } catch (error) { - return setResult(stdin, null, `Error: ${getErrorMessage(error)}`, 1); - } + return runCompiler(runner, ensureLoaded, code, stdin, setResult); }; window.livecodes.fortran = fortranWasm; diff --git a/src/livecodes/languages/haskell-wasm/lang-haskell-wasm-script.ts b/src/livecodes/languages/haskell-wasm/lang-haskell-wasm-script.ts index 4bf5f38bab..e4a6f10ee3 100644 --- a/src/livecodes/languages/haskell-wasm/lang-haskell-wasm-script.ts +++ b/src/livecodes/languages/haskell-wasm/lang-haskell-wasm-script.ts @@ -1,5 +1,6 @@ import { getErrorMessage } from '../../utils/utils'; import { ghcBrowserBaseUrl, haskellWasiShimUrl } from '../../vendors'; +import { getParentOrigin } from '../wasm-runtime'; // @ts-ignore // eslint-disable-next-line import/no-unresolved import workerContent from './lang-haskell-wasm-worker.raw.js?raw'; @@ -135,19 +136,7 @@ const createRunner = (createWorker: () => Worker) => { return { init, run }; }; -const parentOrigin = - window.parent === window - ? window.location.origin - : window.location.ancestorOrigins?.[0] || - (() => { - if (!document.referrer) return '*'; - try { - return new URL(document.referrer).origin; - } catch { - // Ignore malformed referrers and use the wildcard fallback below. - return '*'; - } - })(); +const parentOrigin = getParentOrigin(); window.livecodes.haskellWasm ??= {}; const haskellWasm = window.livecodes.haskellWasm; diff --git a/src/livecodes/languages/v-wasm/__tests__/lang-v-wasm.spec.ts b/src/livecodes/languages/v-wasm/__tests__/lang-v-wasm.spec.ts index c89cfb163b..ec6348854e 100644 --- a/src/livecodes/languages/v-wasm/__tests__/lang-v-wasm.spec.ts +++ b/src/livecodes/languages/v-wasm/__tests__/lang-v-wasm.spec.ts @@ -24,8 +24,8 @@ describe('V (Wasm) language spec', () => { test('uses a code editor with V support', () => { expect(vWasm.editor).toBe('script'); - // There is no dedicated V mode, so the Go mode stands in for the editors that lack one. - expect(vWasm.editorSupport?.monaco?.language).toBe('go'); + // Monaco ships a V mode; CodeMirror has none, so the Go mode stands in there. + expect(vWasm.editorSupport?.monaco?.language).toBe('v'); // Prism does ship a V grammar, so CodeJar uses the real one. expect(vWasm.editorSupport?.codejar?.language).toBe('v'); }); diff --git a/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts b/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts index 7da023dedb..26dc50fec7 100644 --- a/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts +++ b/src/livecodes/languages/v-wasm/lang-v-wasm-script.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ -import { getErrorMessage } from '../../utils/utils'; import { clangWasmBaseUrl, vWasmBaseUrl } from '../../vendors'; +import { createLoadingReporter, runCompiler } from '../wasm-runtime'; import { createWorkerRunner, type Runner } from '../worker-runner'; // One runner for one language. The compiler is @live-codes/v-wasm, loaded from its CDN build: the @@ -127,25 +127,8 @@ vWasm.failed = false; // V compiler plus Clang toolchain are not downloaded again, instead of spawning a new one. vWasm.runner ??= null; -// The result runs in an iframe; post status updates to the app origin. Mirrors -// the other WASM language scripts (`fanak`, `haskell-wasm`). -const parentOrigin = - window.parent === window - ? window.location.origin - : window.location.ancestorOrigins?.[0] || - (() => { - if (!document.referrer) return '*'; - try { - return new URL(document.referrer).origin; - } catch { - // Ignore malformed referrers and use the wildcard fallback below. - return '*'; - } - })(); - -const postLoading = (payload: boolean) => { - parent.postMessage({ type: 'loading', payload }, parentOrigin); // NOSONAR - fallback is safe with source/origin checks in the parent. -}; +// The result runs in an iframe; post status updates to the app origin. +const postLoading = createLoadingReporter(); /** Start (once) downloading the compiler, showing the loading indicator while it happens. */ const ensureLoaded = (runner: Runner): Promise => { @@ -198,23 +181,7 @@ vWasm.run = async (input?: string) => { const runner = (vWasm.runner = vWasm.runner || createWorkerRunner({ getWorkerSrc, label: 'V', timeoutMs: RUN_TIMEOUT_MS })); - try { - await ensureLoaded(runner); - } catch (error) { - return setResult(stdin, null, `Error: ${getErrorMessage(error)}`, 1); - } - - try { - const result = await runner.run(code, stdin); - // `errors` holds the compiler's diagnostics and is empty when the program ran. - const errors = (result.errors || []).filter(Boolean); - if (errors.length) { - return setResult(stdin, null, errors.join('\n'), result.exitCode ?? 1); - } - return setResult(stdin, result.output ?? '', null, result.exitCode ?? 0); - } catch (error) { - return setResult(stdin, null, `Error: ${getErrorMessage(error)}`, 1); - } + return runCompiler(runner, ensureLoaded, code, stdin, setResult); }; window.livecodes.v = vWasm; diff --git a/src/livecodes/languages/wasm-runtime.ts b/src/livecodes/languages/wasm-runtime.ts new file mode 100644 index 0000000000..253e932aa1 --- /dev/null +++ b/src/livecodes/languages/wasm-runtime.ts @@ -0,0 +1,67 @@ +import { getErrorMessage } from '../utils/utils'; +import type { Runner } from './worker-runner'; + +/** + * The origin the result page posts status messages to. The result runs in an iframe, so the app + * origin is the parent's origin, the first ancestor's, or the referrer's; the wildcard is a last + * resort and is only safe because the parent checks the source/origin of incoming messages. + */ +export const getParentOrigin = () => + window.parent === window + ? window.location.origin + : window.location.ancestorOrigins?.[0] || + (() => { + if (!document.referrer) return '*'; + try { + return new URL(document.referrer).origin; + } catch { + // Ignore malformed referrers and use the wildcard fallback below. + return '*'; + } + })(); + +/** Reports loading-state changes to the app origin. */ +export const createLoadingReporter = () => { + const parentOrigin = getParentOrigin(); + return (payload: boolean) => { + parent.postMessage({ type: 'loading', payload }, parentOrigin); // NOSONAR - fallback is safe with source/origin checks in the parent. + }; +}; + +export type SetResult = ( + input: string, + output: string | null, + error: string | null, + exitCode: number | null, +) => TResult; + +/** + * Runs `code` on a ready runner and maps the outcome through `setResult`, reporting both the + * compiler's diagnostics and any failure to load the runtime as the result's error. + */ +export const runCompiler = async ( + runner: Runner, + ensureLoaded: (runner: Runner) => Promise, + code: string, + stdin: string, + setResult: SetResult, + options?: unknown, +): Promise => { + try { + await ensureLoaded(runner); + } catch (error) { + return setResult(stdin, null, `Error: ${getErrorMessage(error)}`, 1); + } + + try { + const result = await runner.run(code, stdin, options); + // `errors` holds the compiler's diagnostics and is empty when the program ran. + const errors = (result.errors || []).filter(Boolean); + if (errors.length) { + return setResult(stdin, null, errors.join('\n'), result.exitCode ?? 1); + } + return setResult(stdin, result.output ?? '', null, result.exitCode ?? 0); + } catch (error) { + return setResult(stdin, null, `Error: ${getErrorMessage(error)}`, 1); + } +};