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:
Lumi
2026-06-17 17:47:31 +09:00
committed by GitHub
parent 6774d762ce
commit 938d42ae3f
10 changed files with 406 additions and 0 deletions

6
.github/labeler.yml vendored
View File

@@ -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:

View 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

View 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

View 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

View File

@@ -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';

View 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 [];
}
}

View File

@@ -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',

View File

@@ -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';

View 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());

View File

@@ -95,6 +95,7 @@ export type LanguageKey =
| 'ptx'
| 'racket'
| 'raku'
| 'razorforge'
| 'ruby'
| 'rust'
| 'sail'