From ddf5e8d02b758fd96c6d5bb85c37c58609704173 Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Sun, 4 Oct 2026 22:31:51 +0300 Subject: [PATCH 1/6] feat(Compilers): add Fortran support --- README.md | 2 +- docs/docs/languages/fortran.mdx | 136 +++++++ docs/src/components/LanguageSliders.tsx | 1 + docs/src/components/TemplateList.tsx | 1 + e2e/specs/fortran.spec.ts | 30 ++ 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/fortran.svg | 9 + src/livecodes/html/language-info.html | 31 ++ .../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 + .../__tests__/lang-fortran-wasm.spec.ts | 30 ++ src/livecodes/languages/fortran-wasm/index.ts | 1 + .../fortran-wasm/lang-fortran-wasm-script.ts | 335 ++++++++++++++++++ .../fortran-wasm/lang-fortran-wasm.ts | 48 +++ src/livecodes/languages/languages.ts | 2 + src/livecodes/models.ts | 1 + .../templates/starter/fortran-wasm-starter.ts | 67 ++++ src/livecodes/templates/starter/index.ts | 2 + src/livecodes/vendors.ts | 8 + src/sdk/models.ts | 11 + .../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 | 6 + 32 files changed, 752 insertions(+), 1 deletion(-) create mode 100644 docs/docs/languages/fortran.mdx create mode 100644 e2e/specs/fortran.spec.ts create mode 100644 src/livecodes/assets/templates/fortran.svg create mode 100644 src/livecodes/languages/fortran-wasm/__tests__/lang-fortran-wasm.spec.ts create mode 100644 src/livecodes/languages/fortran-wasm/index.ts create mode 100644 src/livecodes/languages/fortran-wasm/lang-fortran-wasm-script.ts create mode 100644 src/livecodes/languages/fortran-wasm/lang-fortran-wasm.ts create mode 100644 src/livecodes/templates/starter/fortran-wasm-starter.ts diff --git a/README.md b/README.md index 332177b006..8cc8a1d294 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-114-blue)](https://livecodes.io/docs/languages/) +[![LiveCodes: languages](https://img.shields.io/badge/languages-115-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/fortran.mdx b/docs/docs/languages/fortran.mdx new file mode 100644 index 0000000000..fae1de3367 --- /dev/null +++ b/docs/docs/languages/fortran.mdx @@ -0,0 +1,136 @@ +# Fortran + +Fortran in LiveCodes is compiled to WebAssembly by [LFortran](https://lfortran.org/) and run in the +browser, with no server. There is no separate link step: a browser has no linker subprocess to hand a +binary to, so the compiler emits the program as a wasm module and loads it in place. + +Everything happens in the tab. The compiler is about **19 MB compressed** and is fetched once, the +first time Fortran is run; the loading indicator is shown while it downloads, and the same worker is +reused afterwards. + +This is modern Fortran (free form), not a Fortran 77 subset. + +## Usage + +Demo: + +import LiveCodes from '../../src/components/LiveCodes.tsx'; + +export const fortranConfig = { + activeEditor: 'script', + script: { + language: 'fortran', + content: `program hello +print *, 'Hello, World!' +end program`, + }, + 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.fortran` object: + +- `livecodes.fortran.input`: the standard input passed to the program. +- `livecodes.fortran.loaded`: a promise that resolves when the compiler has downloaded and the program + has run. Other helpers should be used after it resolves. +- `livecodes.fortran.output`: the program's standard output. +- `livecodes.fortran.error`: the compiler diagnostics, if the code failed to compile. +- `livecodes.fortran.exitCode`: the exit code of the program. +- `livecodes.fortran.run`: compiles and runs the code again with new input, returning a promise that + resolves with `output`, `error` and `exitCode`. + +Example: + + + +The same API is also available as `livecodes.f90`. + +```fortran +program hello +print *, 'Hello, World!' +end program +``` + +Array sections, whole-array arithmetic and intrinsics behave as expected: + +```fortran +program slice +real :: a(4) +a = [10.0, 20.0, 30.0, 40.0] +print *, a(2:3) ! 20.0 and 30.0, not all four elements +print *, sum(a) ! 100.0 +end program +``` + +`READ` reads from the **stdin** pane, in the same way as the other console languages. + +## Language Info + +### Name + +`fortran` + +### Aliases / Extensions + +`fortran`, `f90`, `f95`, `f03`, `f08`, `f`, `for`, `ftn`, `fortran-wasm`, `lfortran` + +### Editor + +`script` + +## Compiler + +[@live-codes/lfortran-wasm](https://www.npmjs.com/package/@live-codes/lfortran-wasm) + +The package builds LFortran with its LLVM backend and LLD, and exposes `createCompiler()` → +`run(code, stdin)` returning `{ stdout, errors, exitCode }`. Its wasm is served gzipped and +decompressed in the client. + +### Version + +LFortran 0.65.0, with LLVM 22.1.8 and LLD, built with Emscripten 4.0.9. + +## Custom Settings + +Not currently supported. + +## Code Formatting + +Not currently supported. + +## Live Reload + +Live reload is supported. As with the other wasm languages, you can opt out for a page with the +comment `// __livecodes_reload__`. + +## Example Usage + +```fortran +program stats +integer :: i +real :: x(5), total +x = [1.0, 2.0, 3.0, 4.0, 5.0] +total = 0.0 +do i = 1, 5 + total = total + x(i) +end do +print *, 'Sum = ', total +print *, 'Mean = ', total / 5.0 +end program +``` + +## Links + +- [LFortran](https://lfortran.org/) +- [Fortran language website](https://fortran-lang.org/) +- [Learn Fortran](https://fortran-lang.org/learn/) +- [@live-codes/lfortran-wasm](https://www.npmjs.com/package/@live-codes/lfortran-wasm) +- [LiveCodes Documentation](https://livecodes.io/docs/) diff --git a/docs/src/components/LanguageSliders.tsx b/docs/src/components/LanguageSliders.tsx index aec340f1d8..e87b4a218b 100644 --- a/docs/src/components/LanguageSliders.tsx +++ b/docs/src/components/LanguageSliders.tsx @@ -92,6 +92,7 @@ export default function Sliders() { { name: 'cpp-wasm', title: 'C++ (Wasm)' }, { name: 'objc-wasm', title: 'Objective-C (Wasm)' }, { name: 'objcpp-wasm', title: 'Objective-C++ (Wasm)' }, + { name: 'fortran', title: 'Fortran' }, { 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 31772d078a..16e56b01d9 100644 --- a/docs/src/components/TemplateList.tsx +++ b/docs/src/components/TemplateList.tsx @@ -48,6 +48,7 @@ const templates = [ { name: 'php-wasm', title: 'PHP (Wasm) Starter', thumbnail: 'php.svg' }, { 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: '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/e2e/specs/fortran.spec.ts b/e2e/specs/fortran.spec.ts new file mode 100644 index 0000000000..971b29657e --- /dev/null +++ b/e2e/specs/fortran.spec.ts @@ -0,0 +1,30 @@ +import { expect } from '@playwright/test'; +import { getLoadedApp, runButtonSelector } from '../helpers'; +import { test } from '../test-fixtures'; + +test('Fortran', async ({ page, getTestUrl }) => { + // The compiler is about 19 MB compressed and is downloaded on the first run. + test.slow(); + + await page.goto(getTestUrl({ language: 'fortran', script: 'print *, 6*7' })); + const { app, getResult, waitForResultUpdate } = await getLoadedApp(page); + + await app.click(runButtonSelector); + await waitForResultUpdate(); + + // The program's output is the only thing on the result page. + await expect(getResult().locator('body')).toContainText('42'); +}); + +test('Fortran reports a compile error', async ({ page, getTestUrl }) => { + test.slow(); + + await page.goto(getTestUrl({ language: 'fortran', script: 'this is not fortran' })); + const { app, getResult, waitForResultUpdate } = await getLoadedApp(page); + + await app.click(runButtonSelector); + await waitForResultUpdate(); + + // Diagnostics are rendered by LFortran and shown in the console pane. + await expect(getResult().locator('body')).toContainText(/error/i); +}); diff --git a/functions/vendors/templates.js b/functions/vendors/templates.js index 3f6a851976..347939811e 100644 --- a/functions/vendors/templates.js +++ b/functions/vendors/templates.js @@ -50,6 +50,7 @@ export const starterTemplates = { "c-wasm": "C (Wasm) Starter", "cpp-wasm": "C++ (Wasm) Starter", "objc-wasm": "Objective-C (Wasm) Starter", + "fortran-wasm": "Fortran 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 53bc4f1d29..81a0f32183 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -228,6 +228,7 @@ const iifeBuild = () => 'languages/java/lang-java-script.ts', 'languages/cpp/lang-cpp-script.ts', 'languages/clang-wasm/lang-clang-wasm-script.ts', + 'languages/fortran-wasm/lang-fortran-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 5b465e2502..cfa96c36b4 100644 --- a/server/php/inc/starter-templates.json +++ b/server/php/inc/starter-templates.json @@ -50,6 +50,7 @@ "c-wasm": "C (Wasm) Starter", "cpp-wasm": "C++ (Wasm) Starter", "objc-wasm": "Objective-C (Wasm) Starter", + "fortran-wasm": "Fortran 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 62a0f18e71..d03bd476f2 100644 --- a/src/livecodes/UI/command-menu-actions.ts +++ b/src/livecodes/UI/command-menu-actions.ts @@ -306,6 +306,7 @@ export const getCommandMenuActions = ({ 'c-wasm', 'cpp-wasm', 'objc-wasm', + 'fortran-wasm', 'rust-wasm', 'swift-wasm', 'zig-wasm', diff --git a/src/livecodes/assets/templates/fortran.svg b/src/livecodes/assets/templates/fortran.svg new file mode 100644 index 0000000000..3f49819e71 --- /dev/null +++ b/src/livecodes/assets/templates/fortran.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/livecodes/html/language-info.html b/src/livecodes/html/language-info.html index e18804b742..70c12dc604 100644 --- a/src/livecodes/html/language-info.html +++ b/src/livecodes/html/language-info.html @@ -769,6 +769,37 @@

Flow

+
+

Fortran

+
+ Modern Fortran compiled to WebAssembly by + LFortran, running entirely in + the browser. There is no server and no link step: the compiler emits the program as a wasm module + and runs it in place. It is about 19 MB compressed, fetched once when the language is first + run. +
+ +

F#

diff --git a/src/livecodes/i18n/locales/en/language-info.lokalise.json b/src/livecodes/i18n/locales/en/language-info.lokalise.json index 956ae87da4..103a3adde7 100644 --- a/src/livecodes/i18n/locales/en/language-info.lokalise.json +++ b/src/livecodes/i18n/locales/en/language-info.lokalise.json @@ -348,6 +348,18 @@ "notes": "", "translation": "Flow" }, + "fortran.desc": { + "notes": "### ###\n\n\n", + "translation": "Modern Fortran compiled to WebAssembly by LFortran, running entirely in the browser. There is no server and no link step: the compiler emits the program as a wasm module and runs it in place. It is about 19 MB compressed, fetched once when the language is first run." + }, + "fortran.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": " Learn Fortran LFortran official website Fortran language website LiveCodes Documentation Load starter template " + }, + "fortran.name": { + "notes": "", + "translation": "Fortran" + }, "fsharp.desc1": { "notes": "", "translation": "F# is a cross-platform, open-source functional programming language that runs on the .NET platform." diff --git a/src/livecodes/i18n/locales/en/language-info.ts b/src/livecodes/i18n/locales/en/language-info.ts index b203fa7174..6f8da2e8c1 100644 --- a/src/livecodes/i18n/locales/en/language-info.ts +++ b/src/livecodes/i18n/locales/en/language-info.ts @@ -157,6 +157,11 @@ const languageInfo = { link: '<1> <2>Flow official website <3> <4>Flow documentation ', name: 'Flow', }, + fortran: { + desc: 'Modern Fortran compiled to WebAssembly by <1>LFortran, running entirely in the browser. There is no server and no link step: the compiler emits the program as a wasm module and runs it in place. It is about 19 MB compressed, fetched once when the language is first run.', + link: '<1> <2>Learn Fortran <3> <4>LFortran official website <5> <6>Fortran language website <7> <8>LiveCodes Documentation <9> <10>Load starter template ', + name: 'Fortran', + }, fsharp: { desc1: 'F# is a cross-platform, open-source functional programming language that runs on the .NET platform.', diff --git a/src/livecodes/i18n/locales/en/translation.lokalise.json b/src/livecodes/i18n/locales/en/translation.lokalise.json index 092739ff83..e6f2d89824 100644 --- a/src/livecodes/i18n/locales/en/translation.lokalise.json +++ b/src/livecodes/i18n/locales/en/translation.lokalise.json @@ -2648,6 +2648,10 @@ "notes": "", "translation": "Fennel Starter" }, + "templates.starter.fortran-wasm": { + "notes": "", + "translation": "Fortran Starter" + }, "templates.starter.fsharp": { "notes": "", "translation": "F# Starter" diff --git a/src/livecodes/i18n/locales/en/translation.ts b/src/livecodes/i18n/locales/en/translation.ts index 6510d84663..89958f0bde 100644 --- a/src/livecodes/i18n/locales/en/translation.ts +++ b/src/livecodes/i18n/locales/en/translation.ts @@ -1008,6 +1008,7 @@ const translation = { elm: 'Elm Starter', fanak: 'Fanak Starter', fennel: 'Fennel Starter', + 'fortran-wasm': 'Fortran Starter', fsharp: 'F# Starter', 'fsharp-wasm': 'F# (Wasm) Starter', gleam: 'Gleam Starter', diff --git a/src/livecodes/languages/fortran-wasm/__tests__/lang-fortran-wasm.spec.ts b/src/livecodes/languages/fortran-wasm/__tests__/lang-fortran-wasm.spec.ts new file mode 100644 index 0000000000..fed3b02f66 --- /dev/null +++ b/src/livecodes/languages/fortran-wasm/__tests__/lang-fortran-wasm.spec.ts @@ -0,0 +1,30 @@ +import { fortranWasm } from '../lang-fortran-wasm'; + +describe('Fortran (Wasm) language spec', () => { + it('is registered as fortran, with the wasm compiler script', () => { + expect(fortranWasm.name).toBe('fortran'); + expect(fortranWasm.title).toBe('Fortran'); + expect(fortranWasm.compiler).toMatchObject({ + scriptType: 'text/fortran-wasm', + compiledCodeLanguage: 'fortran', + liveReload: true, + }); + }); + + it('accepts the common Fortran extensions', () => { + expect(fortranWasm.extensions).toEqual( + expect.arrayContaining(['fortran', 'f90', 'f95', 'f03', 'f08', 'f', 'for', 'ftn']), + ); + }); + + it('warns that the first run is a large download', () => { + // The compiler is ~19 MB compressed; the app shows a loading indicator for it. + expect(fortranWasm.largeDownload).toBe(true); + }); + + it('uses the Fortran editors', () => { + expect(fortranWasm.editor).toBe('script'); + expect(fortranWasm.editorSupport?.monaco?.language).toBe('fortran'); + expect(fortranWasm.editorSupport?.codejar?.language).toBe('fortran'); + }); +}); diff --git a/src/livecodes/languages/fortran-wasm/index.ts b/src/livecodes/languages/fortran-wasm/index.ts new file mode 100644 index 0000000000..e226f00eac --- /dev/null +++ b/src/livecodes/languages/fortran-wasm/index.ts @@ -0,0 +1 @@ +export * from './lang-fortran-wasm'; diff --git a/src/livecodes/languages/fortran-wasm/lang-fortran-wasm-script.ts b/src/livecodes/languages/fortran-wasm/lang-fortran-wasm-script.ts new file mode 100644 index 0000000000..9912b2497c --- /dev/null +++ b/src/livecodes/languages/fortran-wasm/lang-fortran-wasm-script.ts @@ -0,0 +1,335 @@ +/* eslint-disable no-console */ +import { createWorkerFromContent, getErrorMessage } from '../../utils/utils'; +import { fortranWasmBaseUrl } from '../../vendors'; + +// 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. +// It compiles to WebAssembly and runs the program in place, because a browser has no linker +// 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; + error: string | null; + exitCode: number | null; +} + +interface Runner { + ensureReady: () => Promise; + run: (code: string, input: string) => Promise; +} + +interface FortranWasmApi { + ready: 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(fortranWasmBaseUrl + 'dist/lfortran-wasm.global.js')}); + +let compiler = null; + +const getCompiler = () => { + compiler = + compiler || + self.lfortranWasm.createCompiler({ + baseUrl: ${JSON.stringify(fortranWasmBaseUrl + 'assets/')}, + }); + 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.stdout, + // The package reports diagnostics as one rendered string; the runner's contract is a list. + errors: result.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) }), +); +`; + +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; + 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 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) => { + const id = nextId++; + pending[id] = { resolve, reject }; + worker?.postMessage({ id, code, input }); + }), + ); + + return { ensureReady, run }; +}; + +/** + * Reads the program out of the result page. + * + * The code lives in a ` +`.trimStart(), + }, + style: { + language: 'css', + content: ` +.container { + text-align: center; + font: 1em sans-serif; +} +.logo { + width: 120px; +} +pre { + text-align: left; + background: #f4f4f4; + padding: 1em; + border-radius: 6px; + white-space: pre-wrap; +} +`.trimStart(), + }, + script: { + language: 'fortran', + content: ` +program hello +implicit none +real :: x(5) +x = [1.0, 2.0, 3.0, 4.0, 5.0] +print *, 'Hello, World!' +print *, 'sum = ', sum(x) +print *, 'mean = ', sum(x) / size(x) +end program +`.trimStart(), + }, +}; diff --git a/src/livecodes/templates/starter/index.ts b/src/livecodes/templates/starter/index.ts index 07558aa9d1..7b172772c7 100644 --- a/src/livecodes/templates/starter/index.ts +++ b/src/livecodes/templates/starter/index.ts @@ -25,6 +25,7 @@ import { diagramsStarter } from './diagrams-starter'; import { elmStarter } from './elm-starter'; import { fanakStarter } from './fanak-starter'; import { fennelStarter } from './fennel-starter'; +import { fortranWasmStarter } from './fortran-wasm-starter'; import { fsharpStarter } from './fsharp-starter'; import { fsharpWasmStarter } from './fsharp-wasm-starter'; import { gleamStarter } from './gleam-starter'; @@ -137,6 +138,7 @@ export const starterTemplates = [ cWasmStarter, cppWasmStarter, objcWasmStarter, + fortranWasmStarter, rustWasmStarter, swiftWasmStarter, zigWasmStarter, diff --git a/src/livecodes/vendors.ts b/src/livecodes/vendors.ts index 08e728fe74..2efc486166 100644 --- a/src/livecodes/vendors.ts +++ b/src/livecodes/vendors.ts @@ -108,6 +108,12 @@ export const codeiumProviderUrl = /* @__PURE__ */ getUrl( export const codeMirrorBaseUrl = /* @__PURE__ */ getUrl('@live-codes/codemirror@0.4.3/build/'); +// Fortran is not among the grammars @live-codes/codemirror builds, but CodeMirror 6's legacy modes +// include one. Referenced from here like every other CDN asset. +export const codeMirrorLegacyModesBaseUrl = /* @__PURE__ */ getUrl( + '@codemirror/legacy-modes@6.4.0/mode/', +); + export const codemirrorMinimapUrl = /* @__PURE__ */ getUrl( '@replit/codemirror-minimap@0.5.2/dist/index.js', ); @@ -264,6 +270,8 @@ export const fontUbuntuMonoUrl = /* @__PURE__ */ getUrl('@fontsource/ubuntu-mono export const fontVictorMonoUrl = /* @__PURE__ */ getUrl('victormono@1.5.4/dist/index.css'); +export const fortranWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/lfortran-wasm@0.2.0/'); + export const fscreenUrl = /* @__PURE__ */ getUrl('fscreen@1.2.0/dist/fscreen.esm.js'); export const fsharpWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/fsharp-wasm@0.3.0/'); diff --git a/src/sdk/models.ts b/src/sdk/models.ts index 6eb0f544d9..b746207a07 100644 --- a/src/sdk/models.ts +++ b/src/sdk/models.ts @@ -175,6 +175,16 @@ export type Language = | 'wasm.c' | 'clang.c' | 'clang-c' + | 'fortran' + | 'f90' + | 'f95' + | 'f03' + | 'f08' + | 'f' + | 'for' + | 'ftn' + | 'fortran-wasm' + | 'lfortran' | 'cpp-wasm' | 'cppwasm' | 'wasm.cpp' @@ -473,6 +483,7 @@ export type TemplateName = | 'c-wasm' | 'cpp-wasm' | 'objc-wasm' + | 'fortran-wasm' | 'java' | 'csharp-wasm' | 'blazor-wasm' diff --git a/storybook/preact/stories/EmbedOptions/template.stories.ts b/storybook/preact/stories/EmbedOptions/template.stories.ts index 48c6b69067..a9e2264a5f 100644 --- a/storybook/preact/stories/EmbedOptions/template.stories.ts +++ b/storybook/preact/stories/EmbedOptions/template.stories.ts @@ -65,6 +65,7 @@ export const Cpp = livecodesStory({ template: 'cpp' }); 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 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 48c6b69067..a9e2264a5f 100644 --- a/storybook/react/stories/EmbedOptions/template.stories.ts +++ b/storybook/react/stories/EmbedOptions/template.stories.ts @@ -65,6 +65,7 @@ export const Cpp = livecodesStory({ template: 'cpp' }); 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 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 48c6b69067..a9e2264a5f 100644 --- a/storybook/solid/stories/EmbedOptions/template.stories.ts +++ b/storybook/solid/stories/EmbedOptions/template.stories.ts @@ -65,6 +65,7 @@ export const Cpp = livecodesStory({ template: 'cpp' }); 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 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 48c6b69067..a9e2264a5f 100644 --- a/storybook/svelte/stories/EmbedOptions/template.stories.ts +++ b/storybook/svelte/stories/EmbedOptions/template.stories.ts @@ -65,6 +65,7 @@ export const Cpp = livecodesStory({ template: 'cpp' }); 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 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 48c6b69067..a9e2264a5f 100644 --- a/storybook/vue/stories/EmbedOptions/template.stories.ts +++ b/storybook/vue/stories/EmbedOptions/template.stories.ts @@ -65,6 +65,7 @@ export const Cpp = livecodesStory({ template: 'cpp' }); 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 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 48c6b69067..a9e2264a5f 100644 --- a/storybook/web-components/stories/EmbedOptions/template.stories.ts +++ b/storybook/web-components/stories/EmbedOptions/template.stories.ts @@ -65,6 +65,7 @@ export const Cpp = livecodesStory({ template: 'cpp' }); 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 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 a38bc040fa..1df1547e4a 100644 --- a/vendor-licenses.md +++ b/vendor-licenses.md @@ -344,6 +344,12 @@ wabt.js: [Apache-2.0 license](https://github.com/AssemblyScript/wabt.js/blob/182 wacl: [BSD 3-Clause License](https://github.com/ecky-l/wacl/blob/9daacabb0102a9986f33263261350edfeebdd83b/LICENSE) +@live-codes/lfortran-wasm: [MIT License](https://github.com/live-codes/browser-fortran/blob/main/LICENSE) + +LFortran (Wasm): [BSD 3-Clause License](https://github.com/lfortran/lfortran/blob/main/LICENSE) + +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) @wasm-fmt/ruff_fmt: [MIT License](https://github.com/wasm-fmt/ruff_fmt/blob/6dc277e1be013159858c7de6b150063baa7214bb/LICENSE) From f351baf6357c608f1970863f3c1d721bb3b11527 Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Tue, 6 Oct 2026 00:20:18 +0300 Subject: [PATCH 2/6] edit fortran starter template --- .../fortran-wasm/lang-fortran-wasm.ts | 37 ++++----- .../templates/starter/fortran-wasm-starter.ts | 80 +++++++++++++------ src/livecodes/vendors.ts | 8 +- 3 files changed, 70 insertions(+), 55 deletions(-) diff --git a/src/livecodes/languages/fortran-wasm/lang-fortran-wasm.ts b/src/livecodes/languages/fortran-wasm/lang-fortran-wasm.ts index 2d66e81037..9d50195cc5 100644 --- a/src/livecodes/languages/fortran-wasm/lang-fortran-wasm.ts +++ b/src/livecodes/languages/fortran-wasm/lang-fortran-wasm.ts @@ -1,17 +1,7 @@ -import type { LanguageSpecs } from '../../models'; import { codemirrorLegacy } from '../../editor/codemirror/utils'; -import { codeMirrorLegacyModesBaseUrl, monacoLanguagesBaseUrl } from '../../vendors'; +import type { LanguageSpecs } from '../../models'; +import { codeMirrorBaseUrl, monacoLanguagesBaseUrl } from '../../vendors'; -/** - * Fortran, compiled to WebAssembly by LFortran and run in place by `@live-codes/lfortran-wasm`. - * - * There is no separate link step: a browser has no linker subprocess to hand a binary to, so the - * compiler emits the program as a wasm side module and loads it with `dlopen`. That is also why - * `largeDownload` is set — the compiler is ~19 MiB compressed and is fetched once per session. - * - * The shared implementation lives in `lang-fortran-wasm-script.ts`; like the Clang languages, the - * spec's `factory` is a no-op and everything happens in the runner script in the result page. - */ export const fortranWasm: LanguageSpecs = { name: 'fortran', title: 'Fortran', @@ -20,27 +10,30 @@ export const fortranWasm: LanguageSpecs = { factory: () => async (code) => code, scripts: ({ baseUrl }) => [baseUrl + '{{hash:lang-fortran-wasm-script.js}}'], scriptType: 'text/fortran-wasm', - // The language the source is emitted as in the result page. Without it the result page carries no - // script tag of this type, the runner finds no code and quietly does nothing. The Clang languages - // set it for the same reason. compiledCodeLanguage: 'fortran', liveReload: true, }, - extensions: ['fortran', 'f90', 'f95', 'f03', 'f08', 'f', 'for', 'ftn', 'fortran-wasm', 'lfortran'], + extensions: [ + 'fortran', + 'f90', + 'f95', + 'f03', + 'f08', + 'f', + 'for', + 'ftn', + 'fortran-wasm', + 'lfortran', + ], editor: 'script', editorSupport: { monaco: { languageSupport: monacoLanguagesBaseUrl + 'fortran.js', language: 'fortran', }, - // CodeMirror's Fortran support lives in @codemirror/legacy-modes, which @live-codes/codemirror does - // not build, so the legacy stream parser is imported directly. Prism, used by the CodeJar editor, - // ships a Fortran grammar itself. codemirror: { languageSupport: async () => - codemirrorLegacy( - (await import(codeMirrorLegacyModesBaseUrl + 'fortran.js')).fortran, - ), + codemirrorLegacy((await import(codeMirrorBaseUrl + 'codemirror-lang-fortran.js')).fortran), }, codejar: { language: 'fortran' }, }, diff --git a/src/livecodes/templates/starter/fortran-wasm-starter.ts b/src/livecodes/templates/starter/fortran-wasm-starter.ts index 6ceb3750ee..2513ed9322 100644 --- a/src/livecodes/templates/starter/fortran-wasm-starter.ts +++ b/src/livecodes/templates/starter/fortran-wasm-starter.ts @@ -1,8 +1,12 @@ import type { Template } from '../../models'; /** - * The Fortran starter: the smallest program that shows the compiler working, with the - * `livecodes.fortran` API used from the markup so the output is visible without opening the console. + * The Fortran starter, matching the other Wasm starters: the markup drives the program through the + * `livecodes.fortran` API. The program's first line names the language — which is what the heading + * shows — and the second is a counter the button increments by re-running with new input. + * + * The program deliberately avoids `print *` inside a loop: that is a live upstream codegen bug (see the + * package README), and a starter is the wrong place to trip over it. */ export const fortranWasmStarter: Template = { name: 'fortran-wasm', @@ -13,21 +17,49 @@ export const fortranWasmStarter: Template = { language: 'html', content: `
    -

    Fortran

    +

    Hello, World!

    -

    Program output:

    -
    Loading the compiler…
    +

    You clicked 0 times.

    +
    `.trimStart(), @@ -35,32 +67,28 @@ export const fortranWasmStarter: Template = { style: { language: 'css', content: ` -.container { +.container, +.container button { text-align: center; font: 1em sans-serif; } .logo { - width: 120px; -} -pre { - text-align: left; - background: #f4f4f4; - padding: 1em; - border-radius: 6px; - white-space: pre-wrap; + width: 150px; } `.trimStart(), }, script: { language: 'fortran', content: ` -program hello +program counter implicit none -real :: x(5) -x = [1.0, 2.0, 3.0, 4.0, 5.0] -print *, 'Hello, World!' -print *, 'sum = ', sum(x) -print *, 'mean = ', sum(x) / size(x) +integer :: count + +print *, 'Fortran' + +read *, count +count = count + 1 +print *, count end program `.trimStart(), }, diff --git a/src/livecodes/vendors.ts b/src/livecodes/vendors.ts index 2efc486166..8fd81b246f 100644 --- a/src/livecodes/vendors.ts +++ b/src/livecodes/vendors.ts @@ -108,12 +108,6 @@ export const codeiumProviderUrl = /* @__PURE__ */ getUrl( export const codeMirrorBaseUrl = /* @__PURE__ */ getUrl('@live-codes/codemirror@0.4.3/build/'); -// Fortran is not among the grammars @live-codes/codemirror builds, but CodeMirror 6's legacy modes -// include one. Referenced from here like every other CDN asset. -export const codeMirrorLegacyModesBaseUrl = /* @__PURE__ */ getUrl( - '@codemirror/legacy-modes@6.4.0/mode/', -); - export const codemirrorMinimapUrl = /* @__PURE__ */ getUrl( '@replit/codemirror-minimap@0.5.2/dist/index.js', ); @@ -270,7 +264,7 @@ export const fontUbuntuMonoUrl = /* @__PURE__ */ getUrl('@fontsource/ubuntu-mono export const fontVictorMonoUrl = /* @__PURE__ */ getUrl('victormono@1.5.4/dist/index.css'); -export const fortranWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/lfortran-wasm@0.2.0/'); +export const fortranWasmBaseUrl = /* @__PURE__ */ getUrl('@live-codes/lfortran-wasm@0.3.0/'); export const fscreenUrl = /* @__PURE__ */ getUrl('fscreen@1.2.0/dist/fscreen.esm.js'); From 920cb623c20a4aefb1d8a54806d133b167b693e6 Mon Sep 17 00:00:00 2001 From: Hatem Hosny Date: Tue, 6 Oct 2026 01:02:59 +0300 Subject: [PATCH 3/6] edit docs --- .../{fortran.mdx => fortran-wasm.mdx} | 45 +++++++------------ src/livecodes/html/language-info.html | 38 ++++++++++++---- .../locales/en/language-info.lokalise.json | 26 +++++------ .../i18n/locales/en/language-info.ts | 14 +++--- .../fortran-wasm/lang-fortran-wasm.ts | 18 ++------ 5 files changed, 69 insertions(+), 72 deletions(-) rename docs/docs/languages/{fortran.mdx => fortran-wasm.mdx} (63%) diff --git a/docs/docs/languages/fortran.mdx b/docs/docs/languages/fortran-wasm.mdx similarity index 63% rename from docs/docs/languages/fortran.mdx rename to docs/docs/languages/fortran-wasm.mdx index fae1de3367..bd12889b43 100644 --- a/docs/docs/languages/fortran.mdx +++ b/docs/docs/languages/fortran-wasm.mdx @@ -1,25 +1,20 @@ -# Fortran +# Fortran (Wasm) -Fortran in LiveCodes is compiled to WebAssembly by [LFortran](https://lfortran.org/) and run in the -browser, with no server. There is no separate link step: a browser has no linker subprocess to hand a -binary to, so the compiler emits the program as a wasm module and loads it in place. - -Everything happens in the tab. The compiler is about **19 MB compressed** and is fetched once, the -first time Fortran is run; the loading indicator is shown while it downloads, and the same worker is -reused afterwards. +Fortran is a compiled, imperative programming language designed for numeric computation and scientific computing. -This is modern Fortran (free form), not a Fortran 77 subset. +Fortran in LiveCodes is compiled to WebAssembly by [LFortran](https://lfortran.org/) and run in the +browser, with no server. This is modern Fortran (free form), not a Fortran 77 subset. ## Usage Demo: -import LiveCodes from '../../src/components/LiveCodes.tsx'; +import LiveCodes from '../../src/components/LiveCodes.js'; export const fortranConfig = { activeEditor: 'script', script: { - language: 'fortran', + language: 'fortran-wasm', content: `program hello print *, 'Hello, World!' end program`, @@ -51,8 +46,6 @@ Example: -The same API is also available as `livecodes.f90`. - ```fortran program hello print *, 'Hello, World!' @@ -76,11 +69,11 @@ end program ### Name -`fortran` +`fortran-wasm` ### Aliases / Extensions -`fortran`, `f90`, `f95`, `f03`, `f08`, `f`, `for`, `ftn`, `fortran-wasm`, `lfortran` +`f90`, `f95`, `f03`, `f08`, `f`, `for`, `ftn`, `fortran`, `lfortran` ### Editor @@ -88,19 +81,11 @@ end program ## Compiler -[@live-codes/lfortran-wasm](https://www.npmjs.com/package/@live-codes/lfortran-wasm) - -The package builds LFortran with its LLVM backend and LLD, and exposes `createCompiler()` → -`run(code, stdin)` returning `{ stdout, errors, exitCode }`. Its wasm is served gzipped and -decompressed in the client. +[LFortran](https://lfortran.org/) compiled to WebAssembly. ### Version -LFortran 0.65.0, with LLVM 22.1.8 and LLD, built with Emscripten 4.0.9. - -## Custom Settings - -Not currently supported. +LFortran 0.66.0, with LLVM 22.1.8 and LLD, built with Emscripten 4.0.9. ## Code Formatting @@ -108,8 +93,9 @@ Not currently supported. ## Live Reload -Live reload is supported. As with the other wasm languages, you can opt out for a page with the -comment `// __livecodes_reload__`. +By default, new code changes are sent to the result page for re-evaluation without a full page reload. This behavior can be disabled by adding the code comment `// __livecodes_reload__` to the Fortran code, which forces a full page reload. + +This comment can be added in the [`hiddenContent` property of the editor](../configuration/configuration-object.mdx#markup) for embedded playgrounds. ## Example Usage @@ -127,10 +113,13 @@ print *, 'Mean = ', total / 5.0 end program ``` +## Starter Template + +https://livecodes.io/?template=fortran-wasm + ## Links - [LFortran](https://lfortran.org/) - [Fortran language website](https://fortran-lang.org/) - [Learn Fortran](https://fortran-lang.org/learn/) - [@live-codes/lfortran-wasm](https://www.npmjs.com/package/@live-codes/lfortran-wasm) -- [LiveCodes Documentation](https://livecodes.io/docs/) diff --git a/src/livecodes/html/language-info.html b/src/livecodes/html/language-info.html index 70c12dc604..f9f9713781 100644 --- a/src/livecodes/html/language-info.html +++ b/src/livecodes/html/language-info.html @@ -745,6 +745,11 @@

    Fennel

    >Getting Started with Fennel
  • +
  • + Learn X in Y minutes, where X=Fennel +
  • LiveCodes DocumentationsFlow
  • -
    -

    Fortran

    -
    +
    +

    Fortran (Wasm)

    +
    Modern Fortran compiled to WebAssembly by LFortran, running entirely in - the browser. There is no server and no link step: the compiler emits the program as a wasm module - and runs it in place. It is about 19 MB compressed, fetched once when the language is first - run. + the browser.
    -