From d1d0883a86ce7b994083f99f4e69b1982d2180a4 Mon Sep 17 00:00:00 2001 From: Tim Hutt Date: Thu, 17 Apr 2025 23:22:18 +0100 Subject: [PATCH] Add support for the Sail language (#7304) This adds support for the [Sail language](https://github.com/rems-project/sail) - a DSL for defining ISAs. It's not quite ready but I need some help. These are the main remaining issues: 1. When you "link to binary" it does disassemble the binary properly, but the syntax highlighting and line numbers are broken. ![image](https://github.com/user-attachments/assets/2f4fe12c-49b4-4b26-9cb0-7e1666a7b3a2) 2. If you try to execute the code without a `function main() -> unit = ...` then it gives this error in the compiler output: ``` Internal Compiler Explorer error: Error: spawn /tmp/compiler-explorer-compiler2025025-31052-c8gern.pf8t/model.c EACCES at ChildProcess._handle.onexit (node:internal/child_process:285:19) at onErrorNT (node:internal/child_process:483:16) at process.processTicksAndRejections (node:internal/process/task_queues:82:21) Compiler returned: -1 ``` This is weird - it should give a linker with an undefined reference to `zmain`. 3. Sail compiles to C, and then I added extra steps to compile that to binary (if you select `Execute the code`), but as you can see I had to move the binary back over the C file, so in this case `model.c` is actually an ELF file. It works but that seems very weird. There is a `getExecutableFilename()` method I could override, but doing that seems to make it even more confused. 4. I also had to have a fake flag for `binary` because the `filters` don't seem to get set correctly when passed to `runCompiler()`. E.g. `buildExecutable()` doesn't pass them at all. Not sure what is going on there. Seems to work though. Any help appreciated! PS: Sail is a cool language. It has lightweight dependent type for integers and bit vectors, which I haven't demonstrated in the examples yet, but they're neat. --------- Co-authored-by: Matt Godbolt --- etc/config/sail.amazon.properties | 22 +++ etc/config/sail.defaults.properties | 11 ++ examples/sail/basics.sail | 113 ++++++++++++++ examples/sail/bitfields.sail | 35 +++++ examples/sail/bitvectors.sail | 48 ++++++ examples/sail/default.sail | 9 ++ examples/sail/match.sail | 52 +++++++ examples/sail/max_array.sail | 21 +++ examples/sail/sum_over_array.sail | 37 +++++ examples/sail/types.sail | 65 ++++++++ lib/compilers/_all.ts | 1 + lib/compilers/sail.ts | 176 ++++++++++++++++++++++ lib/languages.ts | 11 ++ static/modes/_all.ts | 1 + static/modes/sail-mode.ts | 225 ++++++++++++++++++++++++++++ types/languages.interfaces.ts | 1 + views/resources/logos/sail.svg | 25 ++++ 17 files changed, 853 insertions(+) create mode 100644 etc/config/sail.amazon.properties create mode 100644 etc/config/sail.defaults.properties create mode 100644 examples/sail/basics.sail create mode 100644 examples/sail/bitfields.sail create mode 100644 examples/sail/bitvectors.sail create mode 100644 examples/sail/default.sail create mode 100644 examples/sail/match.sail create mode 100644 examples/sail/max_array.sail create mode 100644 examples/sail/sum_over_array.sail create mode 100644 examples/sail/types.sail create mode 100644 lib/compilers/sail.ts create mode 100644 static/modes/sail-mode.ts create mode 100644 views/resources/logos/sail.svg diff --git a/etc/config/sail.amazon.properties b/etc/config/sail.amazon.properties new file mode 100644 index 000000000..a122c26ae --- /dev/null +++ b/etc/config/sail.amazon.properties @@ -0,0 +1,22 @@ +compilers=&sail + +defaultCompiler=sail_0_18 + +objdumper=/opt/compiler-explorer/gcc-14.2.0/bin/objdump +# The C compiler used to compile the generated .c files to a binary. +cCompiler=/opt/compiler-explorer/gcc-14.2.0/bin/gcc + +group.sail.groupName=Sail +group.sail.compilers=sail_0_18 +group.sail.isSemVer=true +group.sail.baseName=sail + +group.sail.name=Sail +group.sail.exe=sail +group.sail.versionFlag=--version +group.sail.supportsBinary=true +group.sail.supportsExecute=true +group.sail.compilerType=sail + +compiler.sail_0_18.semver=0.18 +compiler.sail_0_18.exe=/opt/compiler-explorer/sail-0.18/bin/sail diff --git a/etc/config/sail.defaults.properties b/etc/config/sail.defaults.properties new file mode 100644 index 000000000..ea84bdfe9 --- /dev/null +++ b/etc/config/sail.defaults.properties @@ -0,0 +1,11 @@ +compilers=sail + +cCompiler=gcc + +compiler.sail.name=Sail +compiler.sail.exe=sail +compiler.sail.versionFlag=--version +compiler.sail.supportsBinary=true +compiler.sail.supportsExecute=true +compiler.sail.compilerType=sail +compiler.sail.objdumper=objdump diff --git a/examples/sail/basics.sail b/examples/sail/basics.sail new file mode 100644 index 000000000..548a58f7e --- /dev/null +++ b/examples/sail/basics.sail @@ -0,0 +1,113 @@ +// This example introduces most basic features of Sail. +/* Comments are C-style. /* Blocks comments can be nested. */ */ + +// Sail supports configurable indexing endianness. Dec is the most common +// option. It must be declared globally. +default Order dec + +// C-style includes are supported. `prelude.sail` is from the Sail standard +// library and includes some common things. +$include + +// Global mutable variables are introduced with `register`. +// They must include a type (int) and can include an initialiser (= 0). +// `int` is the type for all integers (any size, like in Python). +// Try changing this to 10000000000000000000000000000000000000000000000000. +register my_global_int : int = 0 + +// `nat` is the type for natural numbers (>= 0). Try changing the 3 to -3. +register not_negative : nat = 3 + +// There's also a `range(M, N)` type for integers in [M, N] (inclusive). +// Try changing this to 0 +register month : range(1, 12) = 1 + +// Immutable globals variables are declared with let. +// Real numbers are supported, using libgmp (which represents them as +// arbitrary length integer fractions). Although supported, they are very +// rarely used in Sail code. +let pi : real = 3.141 + +// This is a simple function. Sail is expression-based like Rust, OCaml, +// Haskell, etc. Braces are not required for function bodies if they are +// a single expression. +function add_ints(a : int, b : int) -> int = a + b + +// As usual in expression-based languages, if and match are also expressions. +function max1(a : int, b : int) -> int = if a > b then a else b + +// You can use traditional imperative forms too but it isn't recommended. +function max2(a : int, b : int) -> int = { + if a > b then { + return a; + } else { + return b; + } +} + +// It has standard ML/Rust style `match`. +function month_name(month : range(1, 12)) -> string = + match month { + 1 => "Jan", + 2 => "Feb", + // Alternate patterns are not yet supported. + // 3 | 4 => "March or April", + // _ is a wildcard. + _ => "Whatever", + } + +// You can define a `main()` function which will be compiled to `zmain()` in C. +// The name mangling system for C is Z-encoding which basically double all +// z's and prepends a z. E.g. size -> zsizze +// +// Main returns `unit` which is like a 0-length tuple. It's used in places you +// might use `void` in C. Unlike `void` you can make a value of `unit` by writing +// `()`. +// +// Sail will optionally generate a C `main()` function that calls `zmain()` +// (this code). +function main() -> unit = { + // The standard print function. + print_endline("Hello world"); + + // `let` introduces immutable variables. + let a : int = 5; + // They can be shadowed with the same name. + let a : string = "5"; + // Type annotations are not always required - often they can be inferred. + let a = 5; + // Because they are immutable you cannot do this: + a = 6; + + // Mutable variables use `var`. + var a : int = 5; + // This is now ok. + a = 6; + + // One small gotcha is that the inferred type for 5 is int(5). So this + var a = 5; + // is equivalent to this + var a : int(5) = 5; + // And because its type is literally the number 5, you can't assign + // any other numbers to it. + // a = 6; // compile error +} + +// Being aimed at ISA specifications, Sail has very good support for bit vectors. +function reverse_endianness(word : bits(16)) -> bits(16) = + // @ means concatenation. Since we declared `default Order dec` all + // of these indices and concatenations happen in Little Endian notational + // order. This doesn't mean that the underlying data is Little Endian though. + // + // .. selects a bit range. This is inclusive. Currently there is no + // half-open syntax (8 >.. 0) but this is planned. + word[7 .. 0] @ word[15 .. 8] + +// Sail has an advanced type system for bit vectors and integers which means +// they can almost always be checked at compile time. +// +// Try changing 7 to 8 here. +function lowest_byte(word : bits(16)) -> bits(8) = word[7 .. 0] + +// Check the other examples for more features. Or see the Sail manual here: +// https://alasdair.github.io/manual.html diff --git a/examples/sail/bitfields.sail b/examples/sail/bitfields.sail new file mode 100644 index 000000000..609939333 --- /dev/null +++ b/examples/sail/bitfields.sail @@ -0,0 +1,35 @@ +default Order dec + +$include +$include + +// Bitfields are useful for registers. You can take an underlying bitvector +// and allow naming bits in them. + +bitfield Status : bits(32) = { + U : 31, + // Not all bits need to have a corresponding field, and fields can overlap. + A : 0, + lowest_byte : 7 .. 0, + upper_byte : 31 .. 24, +} + +function main() -> unit = { + // To create a new bitfield, use Mk_. + var status = Mk_Status(0x00000000); + + // You can update mutable variables (including `register`s) imperatively: + status[lowest_byte] = status[upper_byte]; + + // You can access the underlying bitvector via the special field `bits`. + status.bits = 0x11223344; + + // You can also do functional-style immutable updates. + let status = [ + status with + U = 0b1, + lowest_byte = 0xFF, + ]; + + print_endline(bits_str(status.bits)); +} diff --git a/examples/sail/bitvectors.sail b/examples/sail/bitvectors.sail new file mode 100644 index 000000000..1e56593ab --- /dev/null +++ b/examples/sail/bitvectors.sail @@ -0,0 +1,48 @@ +default Order dec + +$include +$include + +// Sail has excellent support for bit vectors. These have the type `bits(N)`. +let status : bits(32) = 0x11223344 + +// Bit vector literals are written 0x.. or 0b... +// Leading zeros are significant - the affect the size of the literal. +// This is ok: +register byte0 : bits(8) = 0x01 +// This is an error: +// register byte1 : bits(8) = 0x1 + +function main() -> unit = { + // @ means concatenation. With 'default Order dec' concatenation is written + // in Big Endian order. + assert(0x12 @ 0x34 == 0x1234); + + // Individual bits or ranges of bits can be accessed like this. + // Note that the range is inclusive. + assert(status[0] == bitzero); + assert(status[7 .. 0] == 0x44); + + // You can update mutable bit vectors like this: + byte0[3 .. 0] = 0b1100; + // Or in immutable function style: + let byte2 = [ + byte0 with + 3 .. 0 = 0xA, + ]; + + // You can convert a bit vector to a string with `bits_str()`. If it is + // a multiple of 4 bits it will be printed in hex, otherwise binary. + print_endline(bits_str(byte2[2 .. 0])); + + // Pattern matching also supports bit vectors. + match byte2 { + upper @ 0b010 => { + print_endline(bits_str(upper)); + }, + 0b10 @ lower => { + print_endline(bits_str(lower)); + }, + _ => (), + }; +} diff --git a/examples/sail/default.sail b/examples/sail/default.sail new file mode 100644 index 000000000..c7a0b0630 --- /dev/null +++ b/examples/sail/default.sail @@ -0,0 +1,9 @@ +default Order dec + +$include + +function main() -> unit = { + print_endline("Hello world!"); +} + +// See the examples for a brief introduction to Sail. Start with `basics.sail`. diff --git a/examples/sail/match.sail b/examples/sail/match.sail new file mode 100644 index 000000000..3def61151 --- /dev/null +++ b/examples/sail/match.sail @@ -0,0 +1,52 @@ +default Order dec + +$include +$include + +// Sail has pattern matching similar to Rust, OCaml and other functional languages. +// A notable difference is that it doesn't support combining identical branches. + +function suit_name(suit : range(0, 3)) -> string = + match suit { + 0 => "hearts", + 1 => "diamonds", + 2 => "clubs", + 3 => "spaces", + } + +// _ is a wildcard + +function map_add(a : option(int), b : option(int)) -> option(int) = + match (a, b) { + (Some(a), Some(b)) => Some(a + b), + _ => None(), + } + +// Sail copies a minor footgun from other functional languages. + +function ordinal(x : nat) -> string = + match x { + 0 => "zeroth", + 1 => "first", + 2 => "second", + 3 => "third", + other => concat_str(dec_str(other), "th"), + } + +// Here `other` creates a new variable bound to `x`. +// Unfortunately it can catch you out. The following code appears correct and +// will compile (with warnings), but actually the function always returns 2. +// Rust and OCaml also suffer from this flaw. + +let ONE = 1 +let TWO = 2 +let THREE = 3 + +function add_one(x : range(1, 3)) -> range(2, 4) = + match x { + ONE => 2, + TWO => 3, + THREE => 4, + } + +function main() -> unit = () diff --git a/examples/sail/max_array.sail b/examples/sail/max_array.sail new file mode 100644 index 000000000..8c337b8de --- /dev/null +++ b/examples/sail/max_array.sail @@ -0,0 +1,21 @@ +default Order dec + +$include +$include + +// This is generic over all fixed-length vectors. They are not dynamically sized. +function max_array forall 'n . (x : vector('n, int), y : vector('n, int)) -> vector('n, int) = { + // vector_init() copies the value to all elements. + var m : vector('n, int) = vector_init(0); + foreach (i from 0 to ('n - 1)) { + m[i] = if x[i] > y[i] then x[i] else y[i]; + }; + m +} + +function main() -> unit = { + let m = max_array([1, 2, 3], [3, 2, 1]); + foreach (i from 0 to (length(m) - 1)) { + print_endline(dec_str(m[i])); + } +} diff --git a/examples/sail/sum_over_array.sail b/examples/sail/sum_over_array.sail new file mode 100644 index 000000000..12f9e818e --- /dev/null +++ b/examples/sail/sum_over_array.sail @@ -0,0 +1,37 @@ +default Order dec + +$include +$include + +// This is generic over all fixed-length vectors. `input` is not +// dynamically sized. +function sum_array forall 'n . (input : vector('n, int)) -> int = { + // Type must be explicitly declared as `int`. If you leave + // that out the type will be inferred as `int(0)` and it + // can only contain the value 0. + var total : int = 0; + foreach (i from 0 to (length(input) - 1)) { + // You can easily add `+=` operators - Sail has good + // support for operator overloading - but it isn't + // there by default. + total = total + input[i]; + }; + total +} + +// You can also use functional style lists, but these are +// not supported by all backends. +function sum_array_fp (input : list(int)) -> int = + match input { + // Empty list. List literals are with [| ... |]. + [| |] => 0, + // Single element. + [| el |] => el, + // More than one element. + head :: tail => head + sum_array_fp(tail), + } + +function main() -> unit = { + print_endline(concat_str("Sum of [1, 2, 3] is ", dec_str(sum_array([1 : int, 2, 3])))); + print_endline(concat_str("Sum of [1, 2, 3] is ", dec_str(sum_array_fp([| 1, 2, 3 |])))); +} diff --git a/examples/sail/types.sail b/examples/sail/types.sail new file mode 100644 index 000000000..73025ebfb --- /dev/null +++ b/examples/sail/types.sail @@ -0,0 +1,65 @@ +default Order dec + +$include +$include +$include + +// Basic types are: + +// Any integer, limited only by your computer's memory! +let a : int = 1000000000000000000000000000000000000000000000000000000000000000 + +// Any natural number (non-negative integer). +let b : nat = 1000000000000000000000000000000000000000000000000000000000000000 + +// An inclusive range of integers. +let c : range(1, 6) = 3 + +// Strings. Sail only has very basic string support, mainly for logging and +// assembly/disassembly support. +let d : string = "hello" + +// Boolean +let e : bool = false + +// Unit (like an empty tuple; roughly equivalent to 'void'). +let f : unit = () + +// Bit vector. Note that bit-vector literals are written in hex or binary +// and leading zeros are significant! 0xF is a 4-bit bitvector, but 0x0F is +// an 8-bit bitvector. Try changing this to 0xF or 0b1010101. +let g : bits(8) = 0x0F + +// More complex types are: + +// Standard functional option and result types. +let h : option(int) = Some(5) +let i : option(nat) = None() + +let j : result(int, unit) = Ok(5) +let k : result(int, unit) = Err(()) + +// Fixed-length vectors. +let l : vector(4, int) = [1, 2, 3, 4] + +// Dynamically sized linked lists. +let m : list(int) = [|1, 2, 3, 4|] + +// Custom enum, union and struct types are supported. + +// Enums are not namespaced; so you refer to `Red` directly, not `Primary::Red`. +enum Primary = { Red, Green, Blue } + +// Unions are tagged (same as `enum` in Rust), and decoded using `match`. +union Instruction = { + FullSize : bits(32), + Compressed : bits(16), +} + +struct ExecutionStep = { + pc : bits(32), + instruction : Instruction, + retired : bool, +} + +function main() -> unit = () diff --git a/lib/compilers/_all.ts b/lib/compilers/_all.ts index b5d724431..c910b6ec0 100644 --- a/lib/compilers/_all.ts +++ b/lib/compilers/_all.ts @@ -132,6 +132,7 @@ export {RustCompiler} from './rust.js'; export {RustcCgGCCCompiler} from './rustc-cg-gcc.js'; export {SPIRVCompiler} from './spirv.js'; export {SPIRVToolsCompiler} from './spirv-tools.js'; +export {SailCompiler} from './sail.js'; export {ScalaCompiler} from './scala.js'; export {SdccCompiler} from './sdcc.js'; export {SlangCompiler} from './slang.js'; diff --git a/lib/compilers/sail.ts b/lib/compilers/sail.ts new file mode 100644 index 000000000..b1f746008 --- /dev/null +++ b/lib/compilers/sail.ts @@ -0,0 +1,176 @@ +// 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 fs from 'fs-extra'; + +import {CompilationResult, ExecutionOptionsWithEnv} from '../../types/compilation/compilation.interfaces.js'; +import type {PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js'; +import type {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js'; +import {SelectedLibraryVersion} from '../../types/libraries/libraries.interfaces.js'; +import {BaseCompiler} from '../base-compiler.js'; +import {CompilationEnvironment} from '../compilation-env.js'; + +export class SailCompiler extends BaseCompiler { + // Path to C compiler to use to compile generated C code to binary. + private readonly cCompiler: string; + + static get key() { + return 'sail'; + } + + constructor(info: PreliminaryCompilerInfo, env: CompilationEnvironment) { + super(info, env); + + this.outputFilebase = 'model'; + this.cCompiler = this.compilerProps('cCompiler'); + console.assert(this.cCompiler !== undefined, 'cCompiler not set for Sail compiler'); + } + + override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: any) { + // Target C backend (and override the default C options, -g etc.). + return ['-c']; + } + + override async runCompiler( + compiler: string, + options: string[], + inputFilename: string, + execOptions: ExecutionOptionsWithEnv, + filters?: ParseFiltersAndOutputOptions, + ): Promise { + if (!execOptions) { + execOptions = this.getDefaultExecOptions(); + } + + const tmpDir = path.dirname(inputFilename); + + if (!execOptions.customCwd) { + execOptions.customCwd = tmpDir; + } + + const fullResult: CompilationResult = { + code: 0, + timedOut: false, + stdout: [], + stderr: [], + buildsteps: [], + inputFilename, + }; + + const sailResult = await this.doBuildstepAndAddToResult( + fullResult, + 'Sail to C', + compiler, + [...options, '-o', this.outputFilebase], + execOptions, + ); + + const binary = filters?.binary === true; + + if (sailResult.code !== 0 || !binary) { + return fullResult; + } + + const outputFilenameC = this.getOutputFilename(tmpDir, this.outputFilebase); + const outputFilenameExe = outputFilenameC + '.exe'; + + fullResult.executableFilename = outputFilenameExe; + + // Query the sail compiler via `sail -dir` to find out where the + // C runtime files are (`sail.c` etc). + const sailDirResult = await this.doBuildstepAndAddToResult( + fullResult, + 'Get Sail dir', + compiler, + ['-dir'], + execOptions, + ); + + if (sailDirResult.code !== 0) { + return fullResult; + } + + const sailDir = sailDirResult.stdout + .map(line => line.text) + .join('\n') + .trim(); + + // Now compile the C file to an executable. + const compileResult = await this.doBuildstepAndAddToResult( + fullResult, + 'C to binary', + this.cCompiler, + [ + outputFilenameC, + // Sail C support files. + '-I', + `${sailDir}/lib`, + `${sailDir}/lib/elf.c`, + `${sailDir}/lib/rts.c`, + `${sailDir}/lib/sail.c`, + `${sailDir}/lib/sail_failure.c`, + // Support for .elf.gz files. This has been removed in Sail 0.19 + // so it isn't needed for future versions. + '-lz', + // For arbitrary precision integer types. + '-lgmp', + // Enable optimisations so the assembly isn't hilariously verbose. + // Ideally this would be user configurable but we'd need + // something like `-Wl,..` to pass options through Sail to + // here and that doesn't really exist. + '-O', + '-o', + outputFilenameExe, + ], + execOptions, + ); + + // This is weird, but as far as I can tell CE expects the same + // output file name when compiling to IR (C in this case) + // and when compiling to a binary. If you don't do this it + // tries to do nonsensical things like objdumping the C, so we + // copy the binary back over the C file. + if (compileResult.code === 0 && (await fs.pathExists(outputFilenameExe))) { + console.log(`Copying ${outputFilenameExe} to ${outputFilenameC}`); + await fs.copyFile(outputFilenameExe, outputFilenameC); + } + + return fullResult; + } + + override getOutputFilename(dirPath: string, outputFilebase: string, key?: any): string { + return path.join(dirPath, `${outputFilebase}.c`); + } + + override getLibLinkInfo( + filters: ParseFiltersAndOutputOptions, + libraries: SelectedLibraryVersion[], + toolchainPath: string, + dirPath: string, + ) { + // Prevent any library linking flags from being passed to Sail during compilation. + return {libLinks: [], libPathsAsFlags: [], staticLibLinks: []}; + } +} diff --git a/lib/languages.ts b/lib/languages.ts index 47c906c86..ce32b8f8b 100644 --- a/lib/languages.ts +++ b/lib/languages.ts @@ -738,6 +738,17 @@ const definitions: Record = { monacoDisassembly: null, digitSeparator: '_', }, + sail: { + name: 'Sail', + monaco: 'sail', + extensions: ['.sail'], + alias: [], + logoUrl: 'sail.svg', + logoUrlDark: null, + formatter: null, + previewFilter: null, + monacoDisassembly: null, + }, snowball: { name: 'Snowball', monaco: 'swift', diff --git a/static/modes/_all.ts b/static/modes/_all.ts index e5e201ec2..2d7f1b62c 100644 --- a/static/modes/_all.ts +++ b/static/modes/_all.ts @@ -60,6 +60,7 @@ import './ocaml-mode'; import './odin-mode'; import './openclc-mode'; import './ptx-mode'; +import './sail-mode'; import './slang-mode'; import './spice-mode'; import './spirv-mode'; diff --git a/static/modes/sail-mode.ts b/static/modes/sail-mode.ts new file mode 100644 index 000000000..3ce29823e --- /dev/null +++ b/static/modes/sail-mode.ts @@ -0,0 +1,225 @@ +// 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 { + keywords: [ + 'and', + 'as', + 'assert', + 'backwards', + 'barr', + 'bitfield', + 'bitone', + 'bitzero', + 'Bool', + 'by', + 'cast', + 'catch', + 'clause', + 'config', + 'configuration', + 'constant', + 'constraint', + 'dec', + 'default', + 'depend', + 'do', + 'eamem', + 'effect', + 'else', + 'end', + 'enum', + 'escape', + 'exmem', + 'forall', + 'foreach', + 'forwards', + 'from', + 'function', + 'if', + 'implicit', + 'impure', + 'in', + 'inc', + 'infix', + 'infixl', + 'infixr', + 'Int', + 'let', + 'mapping', + 'match', + 'newtype', + 'nondet', + 'operator', + 'Order', + 'overload', + 'pure', + 'ref', + 'register', + 'repeat', + 'return', + 'rmem', + 'rmemt', + 'rreg', + 'scattered', + 'sizeof', + 'struct', + 'then', + 'throw', + 'to', + 'try', + 'type', + 'Type', + 'undef', + 'undefined', + 'union', + 'unspec', + 'until', + 'val', + 'var', + 'where', + 'while', + 'with', + 'wmem', + 'wmv', + 'wmvt', + 'wreg', + ], + + types: [ + 'vector', + 'bitvector', + 'int', + 'nat', + 'atom', + 'range', + 'unit', + 'bit', + 'real', + 'list', + 'bool', + 'string', + 'bits', + 'option', + ], + + identifier: "[a-zA-Z?_][a-zA-Z?0-9_'#]*", + + // String escape sequences. \\, \", \', \n, \t, \b, \r, \ + // Or hex/dec: \xHHH, \DD + string_escapes: /\\(?:[\\"'ntbr]|$|x[0-9A-Fa-f]{2}|[0-9]{3})/, + + tokenizer: { + root: [ + // Whitespace. + {include: '@whitespace'}, + + // Identifiers and keywords. + [ + /@identifier/, + { + cases: { + '@types': 'type', + '@keywords': 'keyword', + '@default': 'identifier', + }, + }, + ], + + // Special identified used for the not function. + [/~/, 'identifier'], + + // Type variable, e.g. 'n + [/'@identifier/, 'type'], + + // (operator_chars)+, optionally followed by _identifier. + [/[!%&*+\-./:<>=@^|#]+(?:_@identifier)?/, 'operator'], + + // TODO: Handle < > in $include not being an operator. + // Although it probably doesn't matter too much that we colour + // it incorrectly, just for syntax highlighting. + + // Brackets. + [/[{}()[\]<>]/, '@brackets'], + + // Numbers. + [/0b[01_]+/, 'number.binary'], + [/0x[0-9a-fA-F_]+/, 'number.hex'], + [/-?\d*\.\d+/, 'number.float'], + [/-?\d+/, 'number'], + + // TODO: is . on it's own a delimiter or an operator? + + // delimiter: after number because of .\d floats + [/[;,.]/, 'delimiter'], + + // Unterminated string. Any character except " or \, + // or \ followed by any character. Then end of line. + // TODO: I think this doesn't mark unclosed multiline + // strings as invalid. + [/"([^"\\]|\\.)*$/, 'string.invalid'], + // Valid string. + [/"/, 'string', '@string'], + ], + + whitespace: [ + // Whitespace. + [/[ \t\r\n]+/, 'white'], + // Start of block comment. + [/\/\*/, 'comment', '@block_comment'], + // Line comment. + [/\/\/.*$/, 'comment'], + ], + + block_comment: [ + // Not / or *, definitely still in the block. + // This is not strictly necessary but improves efficiency. + [/[^\/*]+/, 'comment'], + // /*, push block comment. + [/\/\*/, 'comment', '@push'], + // */, pop block comment. + ['\\*/', 'comment', '@pop'], + // Anything else, still a comment. + [/./, 'comment'], + ], + + string: [ + // Not \ or " - must still be in the string. + [/[^\\"]+/, 'string'], + // Valid escape sequences, including escaped new lines. + [/@string_escapes/, 'string.escape'], + // Any other escape sequence is invalid. + [/\\./, 'string.escape.invalid'], + // End of string. + [/"/, 'string', '@pop'], + ], + }, + }; +} + +monaco.languages.register({id: 'sail'}); +monaco.languages.setMonarchTokensProvider('sail', definition()); diff --git a/types/languages.interfaces.ts b/types/languages.interfaces.ts index d1199636b..dbf032a03 100644 --- a/types/languages.interfaces.ts +++ b/types/languages.interfaces.ts @@ -87,6 +87,7 @@ export type LanguageKey = | 'racket' | 'ruby' | 'rust' + | 'sail' | 'scala' | 'slang' | 'solidity' diff --git a/views/resources/logos/sail.svg b/views/resources/logos/sail.svg new file mode 100644 index 000000000..8b75bea38 --- /dev/null +++ b/views/resources/logos/sail.svg @@ -0,0 +1,25 @@ + + + + + + + + + +