mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2026-07-16 18:37:07 -04:00
Add RazorForge language support (#8825)
This adds support for [RazorForge](https://github.com/dj-lumiere/razorforge-suflae), a precision-focused, ahead-of-time compiled language that lowers to LLVM IR. It follows `docs/AddingALanguage.md`. ### What's included - `lib/languages.ts` — language definition (`.rf`, Monaco mode `razorforge`, disassembly `llvm-ir`) - `types/languages.interfaces.ts` — `razorforge` language key - `lib/compilers/razorforge.ts` + `lib/compilers/_all.ts` — compiler driver - `static/modes/razorforge-mode.ts` + `static/modes/_all.ts` — Monarch syntax highlighting - `etc/config/razorforge.{defaults,amazon}.properties` — compiler config - `examples/razorforge/default.rf` — default example - `.github/labeler.yml` — `lang-razorforge` label ### Compiler behavior RazorForge's CLI takes verbs + positional arguments only (all build config lives in `razorforge.toml`, not flags). The driver invokes the `build` verb, which runs semantic analysis and code generation and writes LLVM IR next to the source (`example.rf` → `example.ll`) without invoking `opt`/`clang`. The primary output is the emitted LLVM IR, so `supportsIrView` is on and `supportsExecute` is off for now. ### Notes - Install recipe: compiler-explorer/infra#2177 - No logo yet (`logoFilename: null`); happy to add one in a follow-up. - Verified locally: `make`, `biome check`, `tsc`, and `vitest related` all pass; the example compiles to valid LLVM IR with the released `v0.0.3-alpha` build.
This commit is contained in:
6
.github/labeler.yml
vendored
6
.github/labeler.yml
vendored
@@ -361,6 +361,12 @@
|
||||
- 'lib/compilers/racket.ts'
|
||||
- 'etc/config/racket.*.properties'
|
||||
|
||||
'lang-razorforge':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'lib/compilers/razorforge.ts'
|
||||
- 'etc/config/razorforge.*.properties'
|
||||
|
||||
'lang-ruby':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
|
||||
12
etc/config/razorforge.amazon.properties
Normal file
12
etc/config/razorforge.amazon.properties
Normal file
@@ -0,0 +1,12 @@
|
||||
compilers=&razorforge
|
||||
compilerType=razorforge
|
||||
defaultCompiler=razorforge004
|
||||
supportsBinary=false
|
||||
supportsBinaryObject=false
|
||||
supportsExecute=false
|
||||
group.razorforge.compilers=razorforge004
|
||||
group.razorforge.isSemVer=true
|
||||
group.razorforge.baseName=razorforge
|
||||
|
||||
compiler.razorforge004.semver=0.0.4-alpha
|
||||
compiler.razorforge004.exe=/opt/compiler-explorer/razorforge-0.0.4-alpha/RazorForge
|
||||
8
etc/config/razorforge.defaults.properties
Normal file
8
etc/config/razorforge.defaults.properties
Normal file
@@ -0,0 +1,8 @@
|
||||
compilers=razorforgedefault
|
||||
compilerType=razorforge
|
||||
defaultCompiler=razorforgedefault
|
||||
supportsBinary=false
|
||||
supportsBinaryObject=false
|
||||
supportsExecute=false
|
||||
compiler.razorforgedefault.name=RazorForge
|
||||
compiler.razorforgedefault.exe=/opt/compiler-explorer/razorforge-0.0.4-alpha/RazorForge
|
||||
17
examples/razorforge/default.rf
Normal file
17
examples/razorforge/default.rf
Normal file
@@ -0,0 +1,17 @@
|
||||
module Example
|
||||
import IO/Console
|
||||
|
||||
# `!` marks a failable routine. The compiler auto-generates try_/check_/lookup_
|
||||
# variants, so callers choose how to consume failure - no exceptions involved.
|
||||
routine get_text!(n: S64) -> Text
|
||||
when n
|
||||
== 0 => throw DivisionByZeroError()
|
||||
== 1 => return "hello"
|
||||
else => return "world"
|
||||
|
||||
routine start()
|
||||
var m = try_get_text(n: 0)
|
||||
when m
|
||||
is None => show("absent")
|
||||
else v => show(f"present: {v}")
|
||||
return
|
||||
@@ -144,6 +144,7 @@ export {QNXCompiler} from './qnx.js';
|
||||
export {R8Compiler} from './r8.js';
|
||||
export {RacketCompiler} from './racket.js';
|
||||
export {RakuCompiler} from './raku.js';
|
||||
export {RazorForgeCompiler} from './razorforge.js';
|
||||
export {ResolcCompiler} from './resolc.js';
|
||||
export {RGACompiler} from './rga.js';
|
||||
export {RubyCompiler} from './ruby.js';
|
||||
|
||||
79
lib/compilers/razorforge.ts
Normal file
79
lib/compilers/razorforge.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) 2026, 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 {CacheKey, CompilationCacheKey} from '../../types/compilation/compilation.interfaces.js';
|
||||
import type {PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js';
|
||||
import type {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
|
||||
import {BaseCompiler} from '../base-compiler.js';
|
||||
import {CompilationEnvironment} from '../compilation-env.js';
|
||||
|
||||
export class RazorForgeCompiler extends BaseCompiler {
|
||||
static get key() {
|
||||
return 'razorforge';
|
||||
}
|
||||
|
||||
constructor(info: PreliminaryCompilerInfo, env: CompilationEnvironment) {
|
||||
super(info, env);
|
||||
this.outputFilebase = 'example';
|
||||
this.compiler.supportsIrView = true;
|
||||
this.compiler.irArg = [];
|
||||
this.compiler.supportsIntel = false;
|
||||
}
|
||||
|
||||
// RazorForge's CLI takes verbs and positional arguments only — all build
|
||||
// configuration lives in razorforge.toml, not flags. The `build` verb runs
|
||||
// semantic analysis and code generation, writing LLVM IR next to the source
|
||||
// file (example.rf -> example.ll) without invoking opt/clang.
|
||||
override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: string): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
override orderArguments(
|
||||
options: string[],
|
||||
inputFilename: string,
|
||||
libIncludes: string[],
|
||||
libOptions: string[],
|
||||
libPaths: string[],
|
||||
libLinks: string[],
|
||||
userOptions: string[],
|
||||
staticLibLinks: string[],
|
||||
) {
|
||||
return ['build', this.filename(inputFilename)].concat(options, userOptions);
|
||||
}
|
||||
|
||||
// The primary output is the emitted LLVM IR itself.
|
||||
override getOutputFilename(dirPath: string, outputFilebase: string, key?: CacheKey | CompilationCacheKey): string {
|
||||
return path.join(dirPath, `${outputFilebase}.ll`);
|
||||
}
|
||||
|
||||
override getCompilerResultLanguageId(filters?: ParseFiltersAndOutputOptions): string | undefined {
|
||||
return 'llvm-ir';
|
||||
}
|
||||
|
||||
override getSharedLibraryPathsAsArguments(): string[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -831,6 +831,17 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
|
||||
previewFilter: null,
|
||||
monacoDisassembly: null,
|
||||
},
|
||||
razorforge: {
|
||||
name: 'RazorForge',
|
||||
monaco: 'razorforge',
|
||||
extensions: ['.rf'],
|
||||
alias: [],
|
||||
logoFilename: null,
|
||||
logoFilenameDark: null,
|
||||
formatter: null,
|
||||
previewFilter: null,
|
||||
monacoDisassembly: 'llvm-ir',
|
||||
},
|
||||
ruby: {
|
||||
name: 'Ruby',
|
||||
monaco: 'ruby',
|
||||
|
||||
@@ -65,6 +65,7 @@ import './odin-mode';
|
||||
import './openclc-mode';
|
||||
import './ptx-mode';
|
||||
import './perl-concise-mode';
|
||||
import './razorforge-mode';
|
||||
import './rust-mode';
|
||||
import './sail-mode';
|
||||
import './slang-mode';
|
||||
|
||||
270
static/modes/razorforge-mode.ts
Normal file
270
static/modes/razorforge-mode.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
// Copyright (c) 2026, 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 {
|
||||
defaultToken: '',
|
||||
|
||||
keywords: [
|
||||
// Declarations
|
||||
'routine',
|
||||
'entity',
|
||||
'record',
|
||||
'choice',
|
||||
'flags',
|
||||
'crashable',
|
||||
'variant',
|
||||
'protocol',
|
||||
// Bindings
|
||||
'var',
|
||||
'preset',
|
||||
'lateinit',
|
||||
// Visibility / placement
|
||||
'secret',
|
||||
'posted',
|
||||
'common',
|
||||
// Self references
|
||||
'me',
|
||||
'Me',
|
||||
// Conformance and constraints
|
||||
'obeys',
|
||||
'disobeys',
|
||||
'needs',
|
||||
'relates',
|
||||
// Control flow
|
||||
'if',
|
||||
'elseif',
|
||||
'else',
|
||||
'then',
|
||||
'unless',
|
||||
'when',
|
||||
'is',
|
||||
'isnot',
|
||||
'isonly',
|
||||
'loop',
|
||||
'while',
|
||||
'for',
|
||||
'break',
|
||||
'continue',
|
||||
'return',
|
||||
'throw',
|
||||
'absent',
|
||||
'becomes',
|
||||
// Modules
|
||||
'import',
|
||||
'module',
|
||||
// Misc
|
||||
'using',
|
||||
'as',
|
||||
'define',
|
||||
'pass',
|
||||
'with',
|
||||
'given',
|
||||
'in',
|
||||
'notin',
|
||||
'to',
|
||||
'til',
|
||||
'by',
|
||||
'discard',
|
||||
// Logical operators
|
||||
'and',
|
||||
'or',
|
||||
'not',
|
||||
'but',
|
||||
// Literals
|
||||
'true',
|
||||
'false',
|
||||
'None',
|
||||
'none',
|
||||
// Memory / interop
|
||||
'dangerous',
|
||||
'external',
|
||||
'steal',
|
||||
],
|
||||
|
||||
operators: [
|
||||
// Comparison
|
||||
'==',
|
||||
'!=',
|
||||
'<',
|
||||
'<=',
|
||||
'>',
|
||||
'>=',
|
||||
'<=>',
|
||||
// Arithmetic (checked by default)
|
||||
'+',
|
||||
'-',
|
||||
'*',
|
||||
'/',
|
||||
'//',
|
||||
'%',
|
||||
'**',
|
||||
// Wrapping variants
|
||||
'+%',
|
||||
'-%',
|
||||
'*%',
|
||||
'**%',
|
||||
// Clamping variants
|
||||
'+^',
|
||||
'-^',
|
||||
'*^',
|
||||
'**^',
|
||||
// Unchecked variants
|
||||
'+!',
|
||||
'-!',
|
||||
'*!',
|
||||
'/!',
|
||||
'//!',
|
||||
'%!',
|
||||
'**!',
|
||||
// Bitwise and shifts
|
||||
'&',
|
||||
'|',
|
||||
'^',
|
||||
'~',
|
||||
'<<',
|
||||
'>>',
|
||||
'<<<',
|
||||
'>>>',
|
||||
// Assignment
|
||||
'=',
|
||||
'+=',
|
||||
'-=',
|
||||
'*=',
|
||||
'/=',
|
||||
'//=',
|
||||
'%=',
|
||||
'**=',
|
||||
'+%=',
|
||||
'-%=',
|
||||
'*%=',
|
||||
'**%=',
|
||||
'+^=',
|
||||
'-^=',
|
||||
'*^=',
|
||||
'/^=',
|
||||
'**^=',
|
||||
'&=',
|
||||
'|=',
|
||||
'^=',
|
||||
'<<=',
|
||||
'>>=',
|
||||
'<<<=',
|
||||
'>>>=',
|
||||
'??=',
|
||||
// Failure and optionals
|
||||
'!',
|
||||
'!!',
|
||||
'?',
|
||||
'??',
|
||||
// Arrows
|
||||
'->',
|
||||
'=>',
|
||||
],
|
||||
|
||||
symbols: /[=><!~?&|+\-*/^%]+/,
|
||||
escapes: /\\(?:[abfnrtv\\"'0]|x[0-9A-Fa-f]{2}|u\{[0-9A-Fa-f]+\})/,
|
||||
|
||||
tokenizer: {
|
||||
root: [
|
||||
// Annotations: @inline, @readonly, @[readonly, innate]
|
||||
[/@\[[^\]]*\]/, 'annotation'],
|
||||
[/@[a-zA-Z_]\w*/, 'annotation'],
|
||||
|
||||
// Compiler-special members like $create, $destroy, $eq
|
||||
[/\$[a-z_]\w*/, 'identifier.special'],
|
||||
|
||||
// Capitalized keywords first, then types (S64, Text, user types)
|
||||
[/Me\b/, 'keyword'],
|
||||
[/None\b/, 'keyword'],
|
||||
[/[A-Z]\w*/, 'type.identifier'],
|
||||
|
||||
// Identifiers and keywords
|
||||
[
|
||||
/[a-z_]\w*/,
|
||||
{
|
||||
cases: {
|
||||
'@keywords': 'keyword',
|
||||
'@default': 'identifier',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// Whitespace and comments
|
||||
{include: '@whitespace'},
|
||||
|
||||
// Delimiters and brackets
|
||||
[/[{}()[\]]/, '@brackets'],
|
||||
[/[<>](?!@symbols)/, '@brackets'],
|
||||
[
|
||||
/@symbols/,
|
||||
{
|
||||
cases: {
|
||||
'@operators': 'operator',
|
||||
'@default': '',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// Numbers — base-prefixed, floats, then integers; literal suffixes
|
||||
// (1_s64, 2.5f32, 100ms, 4gib, 3j64, 1dn ...) ride along with \w*
|
||||
[/0[xX][0-9a-fA-F_]+\w*/, 'number.hex'],
|
||||
[/0[oO][0-7_]+\w*/, 'number.octal'],
|
||||
[/0[bB][01_]+\w*/, 'number.binary'],
|
||||
[/\d[\d_]*\.\d[\d_]*([eE][-+]?\d+)?\w*/, 'number.float'],
|
||||
[/\d[\d_]*\w*/, 'number'],
|
||||
|
||||
[/[;,.:]/, 'delimiter'],
|
||||
|
||||
// Strings: plain plus r/f/rf/b/br prefixes
|
||||
[/(?:rf|br|[rfb])?"([^"\\]|\\.)*$/, 'string.invalid'],
|
||||
[/(?:rf|br|[rfb])?"/, 'string', '@string'],
|
||||
|
||||
// Character literals
|
||||
[/'[^\\']'/, 'string'],
|
||||
[/(')(@escapes)(')/, ['string', 'string.escape', 'string']],
|
||||
[/'/, 'string.invalid'],
|
||||
],
|
||||
|
||||
whitespace: [
|
||||
[/[ \r\n]+/, 'white'],
|
||||
[/###.*$/, 'comment.doc'],
|
||||
[/#.*$/, 'comment'],
|
||||
],
|
||||
|
||||
string: [
|
||||
[/[^\\"{}]+/, 'string'],
|
||||
[/@escapes/, 'string.escape'],
|
||||
[/\\./, 'string.escape.invalid'],
|
||||
[/\{[^}]*\}/, 'variable'],
|
||||
[/"/, 'string', '@pop'],
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
monaco.languages.register({id: 'razorforge'});
|
||||
monaco.languages.setMonarchTokensProvider('razorforge', definition());
|
||||
@@ -95,6 +95,7 @@ export type LanguageKey =
|
||||
| 'ptx'
|
||||
| 'racket'
|
||||
| 'raku'
|
||||
| 'razorforge'
|
||||
| 'ruby'
|
||||
| 'rust'
|
||||
| 'sail'
|
||||
|
||||
Reference in New Issue
Block a user