diff --git a/.github/labeler.yml b/.github/labeler.yml index 204f71b66..0093a50c7 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -260,6 +260,13 @@ - 'etc/config/modula2.*.properties' - 'static/modes/modula2-mode.ts' +'lang-mojo': + - changed-files: + - any-glob-to-any-file: + - 'lib/compilers/mojo.ts' + - 'etc/config/mojo.*.properties' + - 'static/modes/mojo-mode.ts' + 'lang-nim': - changed-files: - any-glob-to-any-file: diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0ae458661..8b6f79185 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -160,3 +160,4 @@ From oldest to newest contributor, we would like to thank: - [Mathias Gredal](https://github.com/mathiasgredal) - [RongDong Xu](https://github.com/arong) - [Adrien Bertrand](https://github.com/adriweb) +- [Roberto Parolin](https://github.com/rparolin) diff --git a/etc/config/mojo.amazon.properties b/etc/config/mojo.amazon.properties new file mode 100644 index 000000000..5078dc7f1 --- /dev/null +++ b/etc/config/mojo.amazon.properties @@ -0,0 +1,14 @@ +compilers=&mojo +mojo.defaultCompiler=mojo_25_3_0 +mojo.compilerType=mojo + +group.mojo.compilers=mojo_25_3_0:mojo_nightly +group.mojo.isSemVer=true +group.mojo.baseName=Mojo + +compiler.mojo_25_3_0.exe=/opt/compiler-explorer/mojo-25.3.0/bin/mojo +compiler.mojo_25_3_0.semver=25.3.0 + +compiler.mojo_nightly.exe=/opt/compiler-explorer/mojo-nightly/bin/mojo +compiler.mojo_nightly.semver=nightly +compiler.mojo_nightly.isNightly=true diff --git a/etc/config/mojo.defaults.properties b/etc/config/mojo.defaults.properties new file mode 100644 index 000000000..f57e61eac --- /dev/null +++ b/etc/config/mojo.defaults.properties @@ -0,0 +1,9 @@ +compilers=&mojo +mojo.defaultCompiler=mojo_nightly +mojo.compilerType=mojo + +group.mojo.compilers=mojo_nightly + +supportsBinary=true +supportsExecute=true +supportsAsmDocs=true diff --git a/examples/mojo/default.mojo b/examples/mojo/default.mojo new file mode 100644 index 000000000..2c940923e --- /dev/null +++ b/examples/mojo/default.mojo @@ -0,0 +1,2 @@ +fn square(n: Int) -> Int: + return n * n diff --git a/lib/compilers/_all.ts b/lib/compilers/_all.ts index 26b60b336..2ccf7c4bb 100644 --- a/lib/compilers/_all.ts +++ b/lib/compilers/_all.ts @@ -103,6 +103,7 @@ export {LLVMMOSCompiler} from './llvm-mos.js'; export {LLVMmcaTool} from './llvm-mca.js'; export {MLIRCompiler} from './mlir.js'; export {MadPascalCompiler} from './madpascal.js'; +export {MojoCompiler} from './mojo.js'; export {MovfuscatorCompiler} from './movfuscator.js'; export {MrustcCompiler} from './mrustc.js'; export {Msp430Compiler} from './msp430.js'; diff --git a/lib/compilers/argument-parsers.ts b/lib/compilers/argument-parsers.ts index 69d2f410b..963bab567 100644 --- a/lib/compilers/argument-parsers.ts +++ b/lib/compilers/argument-parsers.ts @@ -604,6 +604,13 @@ export class PascalParser extends BaseParser { } } +export class MojoParser extends BaseParser { + static override async parse(compiler: BaseCompiler) { + await this.getOptions(compiler, '-help'); + return compiler; + } +} + export class ICCParser extends GCCParser { static override async setCompilerSettingsFromOptions(compiler: BaseCompiler, options: Record) { const keys = _.keys(options); diff --git a/lib/compilers/mojo.ts b/lib/compilers/mojo.ts new file mode 100644 index 000000000..6f15c26ca --- /dev/null +++ b/lib/compilers/mojo.ts @@ -0,0 +1,59 @@ +// Copyright (c) 2025, Compiler Explorer Authors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import path from 'node:path'; +import type {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js'; +import {BaseCompiler} from '../base-compiler.js'; + +export class MojoCompiler extends BaseCompiler { + static get key() { + return 'mojo'; + } + + constructor(info, env) { + super(info, env); + this.delayCleanupTemp = true; + this.compiler.supportsIrView = true; + this.compiler.irArg = ['--emit=llvm']; + } + + override getOutputFilename(dirPath: string, inputFileBase: string) { + // This method tells CE where to find the assembly output + const outputPath = path.join(dirPath, 'example.s'); + return outputPath; + } + + override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: string, userOptions: string[]) { + if (filters.binary) return ['build']; + return ['build', '--emit=asm']; + } + + override getSharedLibraryPathsAsArguments() { + return []; + } + + override getExecutableFilename(dirPath: string, outputFilebase: string) { + return path.join(dirPath, 'example'); + } +} diff --git a/lib/languages.ts b/lib/languages.ts index 501ca45e0..d74117fe8 100644 --- a/lib/languages.ts +++ b/lib/languages.ts @@ -591,6 +591,18 @@ const definitions: Record = { previewFilter: null, monacoDisassembly: null, }, + mojo: { + name: 'Mojo', + monaco: 'mojo', + extensions: ['.mojo', '.🔥'], + alias: [], + logoUrl: 'mojo.svg', + logoUrlDark: null, + formatter: 'mblack', + previewFilter: null, + monacoDisassembly: null, + digitSeparator: '_', + }, nim: { name: 'Nim', monaco: 'nim', diff --git a/static/modes/_all.ts b/static/modes/_all.ts index 17916c647..9de0b6319 100644 --- a/static/modes/_all.ts +++ b/static/modes/_all.ts @@ -54,6 +54,7 @@ import './jakt-mode'; import './llvm-ir-mode'; import './mlir-mode'; import './modula2-mode'; +import './mojo-mode'; import './nc-mode'; import './nim-mode'; import './nix-mode'; diff --git a/static/modes/mojo-mode.ts b/static/modes/mojo-mode.ts new file mode 100644 index 000000000..861e1b8aa --- /dev/null +++ b/static/modes/mojo-mode.ts @@ -0,0 +1,170 @@ +// Copyright (c) 2025, Compiler Explorer Authors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import * as monaco from 'monaco-editor'; + +function definition(): monaco.languages.IMonarchLanguage { + return { + // Set defaultToken to invalid to see what you do not tokenize yet + defaultToken: 'invalid', + + // C# style strings + escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/, + + registers: /%?\b(r[0-9]+[dbw]?|([er]?([abcd][xhl]|cs|fs|ds|ss|sp|bp|ip|sil?|dil?))|[xyz]mm[0-9]+|sp|fp|lr)\b/, + + intelOperators: /PTR|(D|Q|[XYZ]MM)?WORD/, + + tokenizer: { + root: [ + // Error document + [/^<.*>$/, {token: 'annotation'}], + // inline comments + [/\/\*/, 'comment', '@comment'], + // Label definition (anything looking like a label, followed by anything that's not valid in a demangled + // identifier, until we get to a colon followed by any whitespace. This is to avoid finding the colon in + // a scoped (blah::foo) identifier. + [/^[.a-zA-Z0-9_$?@][^#;/]*:(?=\s)/, {token: 'type.identifier'}], + // Label definition (quoted) + [/^"([^"\\]|\\.)*":/, {token: 'type.identifier'}], + // Label definition (ARM style) + [/^\s*[|][^|]*[|]/, {token: 'type.identifier'}], + // Label definition (CL style). This is pretty hideous: we essentially take anything that ends in spaces + // followed by a definition (PROC, ENDP etc) and assume it's a label. That means we have to use + // backtracking and then a lookahead to ensure we don't consume the definition. As a nod to efficiency + // we assume the line has to start with a non-whitespace character before we go all back-tracky. + // See https://github.com/compiler-explorer/compiler-explorer/issues/1645 for examples. + [/^\S.*?(?=\s+(PROC|ENDP|D[BDWQ]))/, {token: 'type.identifier', next: '@rest'}], + // Constant definition + [/^[.a-zA-Z0-9_$?@][^=]*=/, {token: 'type.identifier'}], + // opcode + [/[.a-zA-Z_][.a-zA-Z_0-9]*/, {token: 'keyword', next: '@rest'}], + // braces and parentheses at the start of the line (e.g. nvcc output) + [/[(){}]/, {token: 'operator', next: '@rest'}], + // msvc can have strings at the start of a line in a inSegDirList + [/`/, {token: 'string.backtick', bracket: '@open', next: '@segDirMsvcstring'}], + + // whitespace + {include: '@whitespace'}, + ], + + rest: [ + // pop at the beginning of the next line and rematch + [/^.*$/, {token: '@rematch', next: '@pop'}], + + [/@registers/, 'variable.predefined'], + [/@intelOperators/, 'annotation'], + // inline comments + [/\/\*/, 'comment', '@comment'], + // CL style post-label definition. + [/PROC|ENDP|D[BDWQ]/, 'keyword'], + + // brackets + [/[{}<>()[\]]/, '@brackets'], + + // ARM-style label reference + [/[|][^|]*[|]*/, 'type.identifier'], + + // numbers + [/\d*\.\d+([eE][-+]?\d+)?/, 'number.float'], + [/([$]|0[xX])[0-9a-fA-F]+/, 'number.hex'], + [/\d+/, 'number'], + // ARM-style immediate numbers (which otherwise look like comments) + [/#-?\d+/, 'number'], + + // operators + [/[-+,*/!:&{}()]/, 'operator'], + + // strings + [/"([^"\\]|\\.)*$/, 'string.invalid'], // non-terminated string + [/"/, {token: 'string.quote', bracket: '@open', next: '@string'}], + // `msvc does this, sometimes' + [/`/, {token: 'string.backtick', bracket: '@open', next: '@msvcstring'}], + [/'/, {token: 'string.singlequote', bracket: '@open', next: '@sstring'}], + + // characters + [/'[^\\']'/, 'string'], + [/(')(@escapes)(')/, ['string', 'string.escape', 'string']], + [/'/, 'string.invalid'], + + // Assume anything else is a label reference. .NET uses ` in some identifiers + [/%?[.?_$a-zA-Z@][.?_$a-zA-Z0-9@`]*/, 'type.identifier'], + + // whitespace + {include: '@whitespace'}, + ], + + comment: [ + [/[^/*]+/, 'comment'], + [/\/\*/, 'comment', '@push'], // nested comment + ['\\*/', 'comment', '@pop'], + [/[/*]/, 'comment'], + ], + + string: [ + [/[^\\"]+/, 'string'], + [/@escapes/, 'string.escape'], + [/\\./, 'string.escape.invalid'], + [/"/, {token: 'string.quote', bracket: '@close', next: '@pop'}], + ], + + msvcstringCommon: [ + [/[^\\']+/, 'string'], + [/@escapes/, 'string.escape'], + [/''/, 'string.escape'], // ` isn't escaped but ' is escaped as '' + [/\\./, 'string.escape.invalid'], + ], + + msvcstring: [ + {include: '@msvcstringCommon'}, + [/'/, {token: 'string.backtick', bracket: '@close', next: '@pop'}], + ], + + segDirMsvcstring: [ + {include: '@msvcstringCommon'}, + [/'/, {token: 'string.backtick', bracket: '@close', switchTo: '@rest'}], + ], + + sstring: [ + [/[^\\']+/, 'string'], + [/@escapes/, 'string.escape'], + [/\\./, 'string.escape.invalid'], + [/'/, {token: 'string.singlequote', bracket: '@close', next: '@pop'}], + ], + + whitespace: [ + [/[ \t\r\n]+/, 'white'], + [/\/\*/, 'comment', '@comment'], + [/\/\/.*$/, 'comment'], + [/[#;\\@].*$/, 'comment'], + ], + }, + }; +} + +const def = definition(); +monaco.languages.register({id: 'mojo'}); +monaco.languages.setMonarchTokensProvider('mojo', def); + +export default def; diff --git a/types/languages.interfaces.ts b/types/languages.interfaces.ts index a8ee99edb..fb59af649 100644 --- a/types/languages.interfaces.ts +++ b/types/languages.interfaces.ts @@ -74,6 +74,7 @@ export type LanguageKey = | 'llvm_mir' | 'mlir' | 'modula2' + | 'mojo' | 'nim' | 'nix' | 'numba' diff --git a/views/resources/logos/mojo.svg b/views/resources/logos/mojo.svg new file mode 100644 index 000000000..73bed8047 --- /dev/null +++ b/views/resources/logos/mojo.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + +