Clojure language support (#8146)

<img width="1405" height="474" alt="Clojure in Compiler Explorer 2"
src="https://github.com/user-attachments/assets/76dfed9b-d0eb-4764-b371-9c6023088a50"
/>

With Macro Expansion:
<img width="1642" height="594" alt="image"
src="https://github.com/user-attachments/assets/8b511af9-3617-426e-868d-5a99e5db5756"
/>

TODO
- [x] Language configuration
- [x] Compile via wrapper
  - Inject namespace if necessary to simplify minimal code sample
  - Parse Unix style command line parameters into compiler bindings
  - Place file in path according to namespace
- [x] Install some versions of Clojure [PR
here](https://github.com/compiler-explorer/infra/pull/1849)
- [x] Macroexpansion view (modeled on Rust macro expansion view)
- [x] Filter out command line options that would break wrapper operation
- [x] ~~Parse `--help` output to a list of options~~ Reverted because
not applicable.
- [x] Short form compiler options
- [x] Support Clojure compiler settings via env var, like
`JAVA_OPTS=-Dclojure.compiler.direct-linking=true
-Dclojure.compiler.elide-meta=[:doc,:file]`

NOT DOING
- [x] ~~Support loading dependencies~~ Non-trivial enhancement. Not
necessary for initial release.

---------

Co-authored-by: Matt Godbolt <matt@godbolt.org>
This commit is contained in:
Frank Leon Rose
2025-10-22 10:04:20 -04:00
committed by GitHub
parent ddfe65620f
commit b9dc265973
30 changed files with 817 additions and 1 deletions

6
.github/labeler.yml vendored
View File

@@ -98,6 +98,12 @@
- 'etc/config/clean.*.properties'
- 'static/modes/clean-mode.ts'
'lang-clojure':
- changed-files:
- any-glob-to-any-file:
- 'lib/compilers/clojure.ts'
- 'etc/config/clojure.*.properties'
'lang-cobol':
- changed-files:
- any-glob-to-any-file:

View File

@@ -167,3 +167,4 @@ From oldest to newest contributor, we would like to thank:
- [Alex Trotta](https://github.com/Ahajha)
- [natinusala](https://github.com/natinusala)
- [LJ](https://github.com/elle-j)
- [Frank Leon Rose](https://github.com/frankleonrose)

View File

@@ -15,6 +15,7 @@ const PANE_DATA_MAP = {
core: {name: 'Core', selector: 'view-haskellCore'},
stg: {name: 'STG', selector: 'view-haskellStg'},
cmm: {name: 'Cmm', selector: 'view-haskellCmm'},
clojure_macro: {name: 'Clojure Macro', selector: 'view-clojuremacroexp'},
dump: {name: 'Tree/RTL', selector: 'view-gccdump'},
tree: {name: 'Tree', selector: 'view-gnatdebugtree'},
debug: {name: 'Debug', selector: 'view-gnatdebug'},

View File

@@ -0,0 +1,60 @@
compilers=&clojure
compilerType=clojure
versionFlag=--version
objdumper=/opt/compiler-explorer/jdk-21.0.2/bin/javap
instructionSet=java
defaultCompiler=clojure1123
demangler=
postProcess=
options=
supportsBinary=false
needsMulti=false
supportsExecute=true
interpreted=true
group.clojure.compilers=clojure1123:clojure1122:clojure1121:clojure1120:clojure1114:clojure1113:clojure1112:clojure1111
group.clojure.groupName=Clojure
group.clojure.baseName=clojure
group.clojure.isSemVer=true
group.clojure.licenseName=Eclipse Public License 1.0
group.clojure.licenseLink=https://github.com/clojure/clojure/blob/master/epl-v10.html
compiler.clojure1123.exe=/opt/compiler-explorer/clojure/1.12.3.1577/bin/clojure
compiler.clojure1123.semver=1.12.3
compiler.clojure1123.java_home=/opt/compiler-explorer/jdk-21.0.2
compiler.clojure1123.runtime=/opt/compiler-explorer/jdk-21.0.2/bin/java
compiler.clojure1122.exe=/opt/compiler-explorer/clojure/1.12.2.1571/bin/clojure
compiler.clojure1122.semver=1.12.2
compiler.clojure1122.java_home=/opt/compiler-explorer/jdk-21.0.2
compiler.clojure1122.runtime=/opt/compiler-explorer/jdk-21.0.2/bin/java
compiler.clojure1121.exe=/opt/compiler-explorer/clojure/1.12.1.1550/bin/clojure
compiler.clojure1121.semver=1.12.1
compiler.clojure1121.java_home=/opt/compiler-explorer/jdk-21.0.2
compiler.clojure1121.runtime=/opt/compiler-explorer/jdk-21.0.2/bin/java
compiler.clojure1120.exe=/opt/compiler-explorer/clojure/1.12.0.1530/bin/clojure
compiler.clojure1120.semver=1.12.0
compiler.clojure1120.java_home=/opt/compiler-explorer/jdk-21.0.2
compiler.clojure1120.runtime=/opt/compiler-explorer/jdk-21.0.2/bin/java
compiler.clojure1114.exe=/opt/compiler-explorer/clojure/1.11.4.1474/bin/clojure
compiler.clojure1114.semver=1.11.4
compiler.clojure1114.java_home=/opt/compiler-explorer/jdk-21.0.2
compiler.clojure1114.runtime=/opt/compiler-explorer/jdk-21.0.2/bin/java
compiler.clojure1113.exe=/opt/compiler-explorer/clojure/1.11.3.1463/bin/clojure
compiler.clojure1113.semver=1.11.3
compiler.clojure1113.java_home=/opt/compiler-explorer/jdk-21.0.2
compiler.clojure1113.runtime=/opt/compiler-explorer/jdk-21.0.2/bin/java
compiler.clojure1112.exe=/opt/compiler-explorer/clojure/1.11.2.1446/bin/clojure
compiler.clojure1112.semver=1.11.2
compiler.clojure1112.java_home=/opt/compiler-explorer/jdk-21.0.2
compiler.clojure1112.runtime=/opt/compiler-explorer/jdk-21.0.2/bin/java
compiler.clojure1111.exe=/opt/compiler-explorer/clojure/1.11.1.1435/bin/clojure
compiler.clojure1111.semver=1.11.1
compiler.clojure1111.java_home=/opt/compiler-explorer/jdk-21.0.2
compiler.clojure1111.runtime=/opt/compiler-explorer/jdk-21.0.2/bin/java

View File

@@ -0,0 +1,18 @@
# Default settings for Clojure/JVM
compilers=&clojure
compilerType=clojure
versionFlag=--version
objdumper=javap
instructionSet=java
group.clojure.compilers=clojuredefault
compiler.clojuredefault.exe=/usr/local/bin/clojure
compiler.clojuredefault.name=clojure default
defaultCompiler=clojuredefault
demangler=
postProcess=
options=
supportsBinary=false
needsMulti=false
supportsExecute=false

View File

@@ -0,0 +1,141 @@
(ns clojure-wrapper
(:require [clojure.java.io :as io]
[clojure.pprint :as pp]
[clojure.string :as str]
[clojure.walk :as walk])
(:import [java.io PushbackReader]))
(def help-text
"Compiler options supported:
-h --help - Shows this text and flags sent to compiler
-dl --direct-linking - Eliminates var indirection in fn invocation
-dlc --disable-locals-clearing - Eliminates instructions setting locals to null
-em --elide-meta [:doc,:arglists,:added,:file,...] - Drops metadata keys from classfiles
-omm --omit-macro-meta - Omit metadata from macro-expanded output")
(defn parse-command-line []
(loop [params {}
positional []
ignored []
args *command-line-args*]
(if-let [arg (first args)]
(case arg
("-h" "--help")
(recur (assoc params :show-help true)
positional ignored (rest args))
"--macro-expand"
(recur (assoc params :macro-expand true)
positional ignored (rest args))
("-omm" "--omit-macro-meta")
(recur (assoc params :print-meta false)
positional ignored (rest args))
("-dlc" "--disable-locals-clearing")
(recur (assoc params :disable-locals-clearing true)
positional ignored (rest args))
("-dl" "--direct-linking")
(recur (assoc params :direct-linking true)
positional ignored (rest args))
("-em" "--elide-meta")
(let [elisions (try (some-> args second read-string) (catch Exception _e))]
(when-not (and (sequential? elisions)
(every? keyword? elisions))
(println (str "Invalid elide-meta parameter: '" (second args) "'\n")
"-em flag must be followed by a vector of keywords, like '-em [:doc,:arglists]'")
(System/exit 1))
(recur (assoc params :elide-meta elisions)
positional ignored (drop 2 args)))
(if (or (re-matches #"-.*" arg)
(not (re-matches #".*\.clj" arg)))
(recur params positional (conj ignored arg) (rest args))
(recur params (conj positional arg) ignored (rest args))))
[params positional ignored])))
(defn forms
([input-file]
;; Default is to load all forms while file is open
(forms input-file doall))
([input-file extract]
(with-open [rdr (-> input-file io/reader PushbackReader.)]
(->> #(try (read rdr) (catch Exception _e nil))
(repeatedly)
(take-while some?)
extract))))
(defn read-namespace [input-file]
(let [parse-ns-name (fn [forms]
(some->> forms
(filter (fn [form]
(and (= 'ns (first form))
(symbol? (second form)))))
first ;; ns form
second ;; namespace symbol
name))]
(forms input-file parse-ns-name)))
(defn ns->filename [namespace]
(-> namespace
(str/replace "." "/")
(str/replace "-" "_")
(str ".clj")))
(defn path-of-file [file]
(.getParent file))
(defn print-macro-expansion [input-file macro-params]
(binding [clojure.pprint/*print-pprint-dispatch* clojure.pprint/code-dispatch
clojure.pprint/*print-right-margin* 60
clojure.pprint/*print-miser-width* 20
*print-meta* (:print-meta macro-params true)]
(doseq [form (forms input-file)]
(pp/pprint (walk/macroexpand-all form))
(println))))
(defn compile-input [input-file {:keys [show-help] :as params}]
(let [working-dir (path-of-file input-file)
namespace (read-namespace input-file)
missing-namespace? (nil? namespace)
namespace (or namespace "sample")
compile-filename (io/file working-dir (ns->filename namespace))
compile-path (path-of-file compile-filename)
compiler-options (select-keys params
[:disable-locals-clearing
:direct-linking
:elide-meta])]
(.mkdirs (io/file working-dir "classes"))
(when compile-path
(.mkdirs (io/file compile-path)))
(with-open [out (io/writer (io/output-stream compile-filename))]
(when missing-namespace?
(let [ns-form (str "(ns " namespace ")")]
(println "Injecting namespace form on first line:" ns-form)
(.write out ns-form)))
(io/copy input-file out))
(when show-help
(when (seq *compiler-options*)
(println "*compiler-options* set via environment:" *compiler-options*))
(when (seq compiler-options)
(println "*compiler-options* set via flags:" compiler-options)))
(binding [*compiler-options* (merge *compiler-options* compiler-options)]
(compile (symbol namespace)))))
(let [[params positional ignored] (parse-command-line)
input-file (io/file (first positional))]
(if (:macro-expand params)
(print-macro-expansion input-file params)
(let [count-ignored (count ignored)]
(doseq [param ignored]
(println (format "unrecognized option '%s' ignored" param)))
(when (pos-int? count-ignored)
(println (format "%d warning%s found" count-ignored
(if (= 1 count-ignored) "" "s"))))
(when (or (:show-help params)
(pos-int? count-ignored))
(println help-text))
(compile-input input-file params))))

View File

@@ -0,0 +1,5 @@
;; Type your code here, or load an example.
(ns example)
(defn square [num]
(* num num))

View File

@@ -1680,6 +1680,10 @@ export class BaseCompiler {
return [{text: 'Internal error; unable to open output path'}];
}
async generateClojureMacroExpansion(inputFilename: string, options: string[]): Promise<ResultLine[]> {
return [{text: 'Clojure Macro Expansion not applicable to current compiler.'}];
}
async processHaskellExtraOutput(outpath: string, output: CompilationResult): Promise<ResultLine[]> {
if (output.code !== 0) {
return [{text: 'Failed to run compiler to get Haskell Core'}];
@@ -2430,6 +2434,7 @@ export class BaseCompiler {
const makeGnatDebugTree = backendOptions.produceGnatDebugTree && this.compiler.supportsGnatDebugViews;
const makeIr = backendOptions.produceIr && this.compiler.supportsIrView;
const makeClangir = backendOptions.produceClangir && this.compiler.supportsClangirView;
const makeClojureMacroExp = backendOptions.produceClojureMacroExp && this.compiler.supportsClojureMacroExpView;
const makeOptPipeline = backendOptions.produceOptPipeline && this.compiler.optPipeline;
const makeRustMir = backendOptions.produceRustMir && this.compiler.supportsRustMirView;
const makeRustMacroExp = backendOptions.produceRustMacroExp && this.compiler.supportsRustMacroExpView;
@@ -2448,6 +2453,7 @@ export class BaseCompiler {
optPipelineResult,
rustHirResult,
rustMacroExpResult,
clojureMacroExpResult,
toolsResult,
] = await Promise.all([
this.runCompiler(this.compiler.exe, options, inputFilenameSafe, execOptions, filters),
@@ -2468,6 +2474,7 @@ export class BaseCompiler {
: undefined,
makeRustHir ? this.generateRustHir(inputFilename, options) : undefined,
makeRustMacroExp ? this.generateRustMacroExpansion(inputFilename, options) : undefined,
makeClojureMacroExp ? this.generateClojureMacroExpansion(inputFilename, options) : undefined,
Promise.all(
this.runToolsOfType(
tools,
@@ -2552,6 +2559,8 @@ export class BaseCompiler {
asmResult.haskellStgOutput = haskellStgResult;
asmResult.haskellCmmOutput = haskellCmmResult;
asmResult.clojureMacroExpOutput = clojureMacroExpResult;
if (asmResult.code !== 0) {
return [{...asmResult, asm: '<Compilation failed>'}, [], []];
}

View File

@@ -47,6 +47,7 @@ export {
} from './clang.js';
export {ClangCLCompiler} from './clangcl.js';
export {CleanCompiler} from './clean.js';
export {ClojureCompiler} from './clojure.js';
export {CLSPVCompiler} from './clspv.js';
export {CMakeScriptCompiler} from './cmakescript.js';
export {CoccinelleCCompiler, CoccinelleCPlusPlusCompiler} from './coccinelle.js';

View File

@@ -701,6 +701,12 @@ export class JavaParser extends BaseParser {
}
}
export class ClojureParser extends BaseParser {
override async parse() {
return this.compiler;
}
}
export class KotlinParser extends BaseParser {
override async parse() {
await this.getOptions('-help');

155
lib/compilers/clojure.ts Normal file
View File

@@ -0,0 +1,155 @@
// 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 fs from 'node:fs/promises';
import path from 'node:path';
import _ from 'underscore';
import type {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 type {ResultLine} from '../../types/resultline/resultline.interfaces.js';
import {CompilationEnvironment} from '../compilation-env.js';
import * as utils from '../utils.js';
import {ClojureParser} from './argument-parsers.js';
import {JavaCompiler} from './java.js';
export class ClojureCompiler extends JavaCompiler {
public compilerWrapperPath: string;
static override get key() {
return 'clojure';
}
javaHome: string;
constructor(compilerInfo: PreliminaryCompilerInfo, env: CompilationEnvironment) {
super(compilerInfo, env);
// Use invalid Clojure filename to avoid clashing with name determined by namespace
this.compileFilename = `example-source${this.lang.extensions[0]}`;
this.javaHome = this.compilerProps<string>(`compiler.${this.compiler.id}.java_home`);
this.compilerWrapperPath =
this.compilerProps('compilerWrapper', '') ||
utils.resolvePathFromAppRoot('etc', 'scripts', 'clojure_wrapper.clj');
this.compiler.supportsClojureMacroExpView = true;
}
override getDefaultExecOptions() {
const execOptions = super.getDefaultExecOptions();
if (this.javaHome) {
execOptions.env.JAVA_HOME = this.javaHome;
}
return execOptions;
}
override filterUserOptions(userOptions: string[]) {
return userOptions.filter(option => {
// Filter out anything that looks like a Clojure source file
// that would confuse the wrapper.
// Also, don't allow users to specify macro expansion mode used
// internally.
return !option.match(/^.*\.clj$/) && option !== '--macro-expand';
});
}
override optionsForFilter(filters: ParseFiltersAndOutputOptions) {
// Forcibly enable javap
filters.binary = true;
return [];
}
override getArgumentParserClass() {
return ClojureParser;
}
override async readdir(dirPath: string): Promise<string[]> {
// Clojure requires recursive walk to find namespace-pathed class files
return fs.readdir(dirPath, {recursive: true});
}
async getClojureClasspathArgument(
dirPath: string,
compiler: string,
execOptions: ExecutionOptionsWithEnv,
): Promise<string[]> {
const pathOption = ['-Spath'];
const output = await this.exec(compiler, pathOption, execOptions);
const cp = dirPath + ':' + output.stdout.trim();
return ['-Scp', cp];
}
override async runCompiler(
compiler: string,
options: string[],
inputFilename: string,
execOptions: ExecutionOptionsWithEnv,
filters?: ParseFiltersAndOutputOptions,
): Promise<CompilationResult> {
if (!execOptions) {
execOptions = this.getDefaultExecOptions();
}
if (!execOptions.customCwd) {
execOptions.customCwd = path.dirname(inputFilename);
}
// The items in 'options' before the source file are user inputs.
const sourceFileOptionIndex = options.findIndex(option => {
return option.endsWith('.clj');
});
const userOptions = options.slice(0, sourceFileOptionIndex);
const classpathArgument = await this.getClojureClasspathArgument(execOptions.customCwd, compiler, execOptions);
const wrapperInvokeArgument = ['-M', this.compilerWrapperPath];
const clojureOptions = _.compact([
...classpathArgument,
...wrapperInvokeArgument,
...userOptions,
inputFilename,
]);
const result = await this.exec(compiler, clojureOptions, execOptions);
return {
...this.transformToCompilationResult(result, inputFilename),
languageId: this.getCompilerResultLanguageId(filters),
instructionSet: this.getInstructionSetFromCompilerArgs(options),
};
}
override async generateClojureMacroExpansion(inputFilename: string, options: string[]): Promise<ResultLine[]> {
// The items in 'options' before the source file are user inputs.
const sourceFileOptionIndex = options.findIndex(option => {
return option.endsWith('.clj');
});
const userOptions = options.slice(0, sourceFileOptionIndex);
const clojureOptions = _.compact([...userOptions, '--macro-expand', inputFilename]);
const output = await this.runCompiler(
this.compiler.exe,
clojureOptions,
inputFilename,
this.getDefaultExecOptions(),
);
if (output.code !== 0) {
return [{text: `Failed to run compiler to get Clojure Macro Expansion`}, ...output.stderr];
}
return output.stdout;
}
}

View File

@@ -103,9 +103,15 @@ export class JavaCompiler extends BaseCompiler implements SimpleOutputFilenameCo
};
}
async readdir(dirPath: string): Promise<string[]> {
// Separate method allows override to find classfiles
// that are not in root of dirPath
return fs.readdir(dirPath);
}
override async objdump(outputFilename: string, result: any, maxSize: number) {
const dirPath = path.dirname(outputFilename);
const files = await fs.readdir(dirPath);
const files = await this.readdir(dirPath);
logger.verbose('Class files: ', files);
const results = await Promise.all(
files

View File

@@ -226,6 +226,17 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
previewFilter: null,
monacoDisassembly: null,
},
clojure: {
name: 'Clojure',
monaco: 'clojure',
extensions: ['.clj'],
alias: [],
logoFilename: 'clojure.svg',
logoFilenameDark: null,
formatter: null,
previewFilter: null,
monacoDisassembly: null,
},
cmake: {
name: 'CMake',
monaco: 'cmake',

50
public/logos/clojure.svg Normal file
View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="256"
height="256"
id="svg2">
<metadata
id="metadata4">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6" />
<path
d="M 127.999,0 C 57.423,0 0,57.423 0,128.001 0,198.585 57.423,256.005 127.999,256.005 198.577,256.005 256,198.585 256,128.001 256,57.423 198.577,0 127.999,0"
id="path8"
style="fill:#ffffff" />
<path
d="m 123.318,130.303 c -1.15,2.492 -2.419,5.292 -3.733,8.272 -4.645,10.524 -9.789,23.33 -11.668,31.534 -0.675,2.922 -1.093,6.543 -1.085,10.558 0,1.588 0.085,3.257 0.22,4.957 6.567,2.413 13.66,3.74 21.067,3.753 6.743,-0.013 13.221,-1.127 19.284,-3.143 -1.425,-1.303 -2.785,-2.692 -4.023,-4.257 -8.22,-10.482 -12.806,-25.844 -20.062,-51.674"
id="path10"
style="fill:#91dc47" />
<path
d="m 92.97,78.225 c -15.699,11.064 -25.972,29.312 -26.011,49.992 0.039,20.371 10.003,38.383 25.307,49.493 3.754,-15.637 13.164,-29.955 27.275,-58.655 -0.838,-2.302 -1.793,-4.822 -2.862,-7.469 -3.909,-9.806 -9.551,-21.194 -14.586,-26.351 -2.567,-2.694 -5.682,-5.022 -9.123,-7.01"
id="path12"
style="fill:#91dc47" />
<path
d="m 181.394,198.367 c -8.1,-1.015 -14.785,-2.24 -20.633,-4.303 -9.836,4.884 -20.913,7.643 -32.642,7.643 -40.584,0 -73.483,-32.894 -73.488,-73.49 0,-22.027 9.704,-41.773 25.056,-55.24 -4.106,-0.992 -8.388,-1.571 -12.762,-1.563 -21.562,0.203 -44.323,12.136 -53.799,44.363 -0.886,4.691 -0.675,8.238 -0.675,12.442 0,63.885 51.791,115.676 115.671,115.676 39.122,0 73.682,-19.439 94.611,-49.169 -11.32,2.821 -22.206,4.17 -31.528,4.199 -3.494,0 -6.774,-0.187 -9.811,-0.558"
id="path14"
style="fill:#63b132" />
<path
d="m 159.658,175.953 c 0.714,0.354 2.333,0.932 4.586,1.571 15.157,-11.127 25.007,-29.05 25.046,-49.307 l -0.006,0 c -0.057,-33.771 -27.386,-61.096 -61.165,-61.163 -6.714,0.013 -13.164,1.121 -19.203,3.122 12.419,14.156 18.391,34.386 24.168,56.515 0.003,0.01 0.008,0.018 0.01,0.026 0.011,0.018 1.848,6.145 5.002,14.274 3.132,8.118 7.594,18.168 12.46,25.492 3.195,4.908 6.709,8.435 9.102,9.47"
id="path16"
style="fill:#90b4fe" />
<path
d="m 128.122,12.541 c -38.744,0 -73.016,19.073 -94.008,48.318 10.925,-6.842 22.08,-9.31 31.815,-9.222 13.446,0.039 24.017,4.208 29.089,7.06 1.225,0.706 2.388,1.466 3.527,2.247 9.05,-3.986 19.05,-6.215 29.574,-6.215 40.589,0.005 73.493,32.899 73.499,73.488 l -0.006,0 c 0,20.464 -8.37,38.967 -21.863,52.291 3.312,0.371 6.844,0.602 10.451,0.584 12.811,0.006 26.658,-2.821 37.039,-11.552 6.769,-5.702 12.44,-14.051 15.585,-26.569 0.615,-4.835 0.969,-9.75 0.969,-14.752 0,-63.882 -51.786,-115.678 -115.671,-115.678"
id="path18"
style="fill:#5881d8" />
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -75,6 +75,7 @@ export const GNAT_DEBUG_TREE_VIEW_COMPONENT_NAME = 'gnatdebugtree' as const;
export const GNAT_DEBUG_VIEW_COMPONENT_NAME = 'gnatdebug' as const;
export const RUST_MACRO_EXP_VIEW_COMPONENT_NAME = 'rustmacroexp' as const;
export const RUST_HIR_VIEW_COMPONENT_NAME = 'rusthir' as const;
export const CLOJURE_MACRO_EXP_VIEW_COMPONENT_NAME = 'clojuremacroexp' as const;
export const DEVICE_VIEW_COMPONENT_NAME = 'device' as const;
export const EXPLAIN_VIEW_COMPONENT_NAME = 'explain' as const;
@@ -330,6 +331,15 @@ export type PopulatedRustHirViewState = StateWithId & {
treeid: number;
};
export type EmptyClojureMacroExpViewState = EmptyState;
export type PopulatedClojureMacroExpViewState = StateWithId & {
source: string;
clojureMacroExpOutput: unknown;
compilerName: string;
editorid: number;
treeid: number;
};
export type EmptyDeviceViewState = EmptyState;
export type PopulatedDeviceViewState = StateWithId & {
source: string;
@@ -379,6 +389,7 @@ export interface ComponentStateMap {
[GNAT_DEBUG_VIEW_COMPONENT_NAME]: EmptyGnatDebugViewState | PopulatedGnatDebugViewState;
[RUST_MACRO_EXP_VIEW_COMPONENT_NAME]: EmptyRustMacroExpViewState | PopulatedRustMacroExpViewState;
[RUST_HIR_VIEW_COMPONENT_NAME]: EmptyRustHirViewState | PopulatedRustHirViewState;
[CLOJURE_MACRO_EXP_VIEW_COMPONENT_NAME]: EmptyClojureMacroExpViewState | PopulatedClojureMacroExpViewState;
[DEVICE_VIEW_COMPONENT_NAME]: EmptyDeviceViewState | PopulatedDeviceViewState;
[EXPLAIN_VIEW_COMPONENT_NAME]: EmptyExplainViewState | PopulatedExplainViewState;
}

View File

@@ -32,6 +32,7 @@ import {
AST_VIEW_COMPONENT_NAME,
CFG_VIEW_COMPONENT_NAME,
CLANGIR_VIEW_COMPONENT_NAME,
CLOJURE_MACRO_EXP_VIEW_COMPONENT_NAME,
COMPILER_COMPONENT_NAME,
CONFORMANCE_VIEW_COMPONENT_NAME,
ComponentConfig,
@@ -909,6 +910,38 @@ export function getRustHirViewWith(
};
}
/** Get an empty Clojure macro exp view component. */
export function getClojureMacroExpView(): ComponentConfig<typeof CLOJURE_MACRO_EXP_VIEW_COMPONENT_NAME> {
return {
type: 'component',
componentName: CLOJURE_MACRO_EXP_VIEW_COMPONENT_NAME,
componentState: {},
};
}
/** Get a Clojure macro exp view with the given configuration. */
export function getClojureMacroExpViewWith(
id: number,
source: string,
clojureMacroExpOutput: unknown,
compilerName: string,
editorid: number,
treeid: number,
): ComponentConfig<typeof CLOJURE_MACRO_EXP_VIEW_COMPONENT_NAME> {
return {
type: 'component',
componentName: CLOJURE_MACRO_EXP_VIEW_COMPONENT_NAME,
componentState: {
id,
source,
clojureMacroExpOutput,
compilerName,
editorid,
treeid,
},
};
}
/** Get an empty device view component. */
export function getDeviceView(): ComponentConfig<typeof DEVICE_VIEW_COMPONENT_NAME> {
return {
@@ -1198,6 +1231,7 @@ function validateComponentState(componentName: string, state: any): boolean {
case GNAT_DEBUG_VIEW_COMPONENT_NAME:
case RUST_MACRO_EXP_VIEW_COMPONENT_NAME:
case RUST_HIR_VIEW_COMPONENT_NAME:
case CLOJURE_MACRO_EXP_VIEW_COMPONENT_NAME:
case DEVICE_VIEW_COMPONENT_NAME:
return true;

View File

@@ -158,6 +158,8 @@ export type EventMap = {
rustMacroExpViewOpened: (compilerId: number) => void;
rustMirViewClosed: (compilerId: number) => void;
rustMirViewOpened: (compilerId: number) => void;
clojureMacroExpViewClosed: (compilerId: number) => void;
clojureMacroExpViewOpened: (compilerId: number) => void;
// TODO: There are no emitters for this event
selectLine: (editorId: number, lineNumber: number) => void;
settingsChange: (newSettings: SiteSettings) => void;

View File

@@ -30,6 +30,7 @@ import {
AST_VIEW_COMPONENT_NAME,
CFG_VIEW_COMPONENT_NAME,
CLANGIR_VIEW_COMPONENT_NAME,
CLOJURE_MACRO_EXP_VIEW_COMPONENT_NAME,
COMPILER_COMPONENT_NAME,
CONFORMANCE_VIEW_COMPONENT_NAME,
DEVICE_VIEW_COMPONENT_NAME,
@@ -65,6 +66,7 @@ import {IdentifierSet} from './identifier-set.js';
import {Ast as AstView} from './panes/ast-view.js';
import {Cfg as CfgView} from './panes/cfg-view.js';
import {Clangir as ClangirView} from './panes/clangir-view.js';
import {ClojureMacroExp as ClojureMacroExpView} from './panes/clojuremacroexp-view.js';
import {Compiler} from './panes/compiler.js';
import {Conformance as ConformanceView} from './panes/conformance-view.js';
import {DeviceAsm as DeviceView} from './panes/device-view.js';
@@ -162,6 +164,9 @@ export class Hub {
this.rustMacroExpViewFactory(c, s),
);
layout.registerComponent(RUST_HIR_VIEW_COMPONENT_NAME, (c: GLC, s: any) => this.rustHirViewFactory(c, s));
layout.registerComponent(CLOJURE_MACRO_EXP_VIEW_COMPONENT_NAME, (c: GLC, s: any) =>
this.clojureMacroExpViewFactory(c, s),
);
layout.registerComponent(GCC_DUMP_VIEW_COMPONENT_NAME, (c: GLC, s: any) => this.gccDumpViewFactory(c, s));
layout.registerComponent(CFG_VIEW_COMPONENT_NAME, (c: GLC, s: any) => this.cfgViewFactory(c, s));
layout.registerComponent(CONFORMANCE_VIEW_COMPONENT_NAME, (c: GLC, s: any) =>
@@ -574,6 +579,13 @@ export class Hub {
return new HaskellCmmView(this, container, state);
}
public clojureMacroExpViewFactory(
container: GoldenLayout.Container,
state: InferComponentState<ClojureMacroExpView>,
): ClojureMacroExpView {
return new ClojureMacroExpView(this, container, state);
}
public gccDumpViewFactory(container: GoldenLayout.Container, state: InferComponentState<GCCDumpView>): GCCDumpView {
return new GCCDumpView(this, container, state);
}

View File

@@ -0,0 +1,27 @@
// 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.
export interface ClojureMacroExpState {
clojureMacroExpOutput: any;
}

View File

@@ -0,0 +1,129 @@
// 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 {Container} from 'golden-layout';
import $ from 'jquery';
import * as monaco from 'monaco-editor';
import _ from 'underscore';
import {CompilationResult} from '../../types/compilation/compilation.interfaces.js';
import {CompilerInfo} from '../../types/compiler.interfaces.js';
import {Hub} from '../hub.js';
import {extendConfig} from '../monaco-config.js';
import {ClojureMacroExpState} from './clojuremacroexp-view.interfaces.js';
import {MonacoPaneState} from './pane.interfaces.js';
import {MonacoPane} from './pane.js';
export class ClojureMacroExp extends MonacoPane<monaco.editor.IStandaloneCodeEditor, ClojureMacroExpState> {
constructor(hub: Hub, container: Container, state: ClojureMacroExpState & MonacoPaneState) {
super(hub, container, state);
if (state.clojureMacroExpOutput) {
this.showClojureMacroExpResults(state.clojureMacroExpOutput);
}
}
override getInitialHTML(): string {
return $('#clojuremacroexp').html();
}
override createEditor(editorRoot: HTMLElement): void {
this.editor = monaco.editor.create(
editorRoot,
extendConfig({
language: 'clojure',
readOnly: true,
glyphMargin: true,
lineNumbersMinChars: 3,
}),
);
}
override getPrintName() {
return 'Clojure Macro Expansion Output';
}
override getDefaultPaneName(): string {
return 'Clojure Macro Expansion Viewer';
}
override registerCallbacks(): void {
const throttleFunction = _.throttle(
(event: monaco.editor.ICursorSelectionChangedEvent) => this.onDidChangeCursorSelection(event),
500,
);
this.editor.onDidChangeCursorSelection(event => throttleFunction(event));
this.eventHub.emit('clojureMacroExpViewOpened', this.compilerInfo.compilerId);
this.eventHub.emit('requestSettings');
}
override onCompileResult(compilerId: number, compiler: CompilerInfo, result: CompilationResult): void {
if (this.compilerInfo.compilerId !== compilerId) return;
if (result.clojureMacroExpOutput) {
this.showClojureMacroExpResults(result.clojureMacroExpOutput);
} else if (compiler.supportsClojureMacroExpView) {
this.showClojureMacroExpResults([{text: '<No output>'}]);
}
}
override onCompiler(
compilerId: number,
compiler: CompilerInfo | null,
options: string,
editorId?: number,
treeId?: number,
): void {
if (this.compilerInfo.compilerId === compilerId) {
this.compilerInfo.compilerName = compiler ? compiler.name : '';
this.compilerInfo.editorId = editorId;
this.compilerInfo.treeId = treeId;
this.updateTitle();
if (compiler && !compiler.supportsClojureMacroExpView) {
this.showClojureMacroExpResults([
{
text: '<Clojure Macro Expansion output is not supported for this compiler>',
},
]);
}
}
}
showClojureMacroExpResults(result: any[]): void {
this.editor
.getModel()
?.setValue(result.length ? _.pluck(result, 'text').join('\n') : '<No Clojure Macro Expansion generated>');
if (!this.isAwaitingInitialResults) {
if (this.selection) {
this.editor.setSelection(this.selection);
this.editor.revealLinesInCenter(this.selection.selectionStartLineNumber, this.selection.endLineNumber);
}
this.isAwaitingInitialResults = true;
}
}
override close(): void {
this.eventHub.unsubscribe();
this.eventHub.emit('clojureMacroExpViewClosed', this.compilerInfo.compilerId);
this.editor.dispose();
}
}

View File

@@ -193,6 +193,7 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
private haskellCoreButton: JQuery<HTMLButtonElement>;
private haskellStgButton: JQuery<HTMLButtonElement>;
private haskellCmmButton: JQuery<HTMLButtonElement>;
private clojureMacroExpButton: JQuery<HTMLButtonElement>;
private gccDumpButton: JQuery<HTMLButtonElement>;
private cfgButton: JQuery<HTMLButtonElement>;
private explainButton: JQuery<HTMLButtonElement>;
@@ -269,6 +270,7 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
private haskellCoreViewOpen: boolean;
private haskellStgViewOpen: boolean;
private haskellCmmViewOpen: boolean;
private clojureMacroExpViewOpen: boolean;
private ppOptions: PPOptions;
private llvmIrOptions: LLVMIrBackendOptions;
private clangirOptions: ClangirBackendOptions;
@@ -623,6 +625,17 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
);
};
const createClojureMacroExpView = () => {
return Components.getClojureMacroExpViewWith(
this.id,
this.source,
this.lastResult?.clojureMacroExpOutput,
this.getCompilerName(),
this.sourceEditorId ?? 0,
this.sourceTreeId ?? 0,
);
};
const createGccDumpView = () => {
return Components.getGccDumpViewWith(
this.id,
@@ -896,6 +909,17 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
insertPoint.addChild(createRustHirView());
});
createDragSource(this.container.layoutManager, this.clojureMacroExpButton, () =>
createClojureMacroExpView(),
).on('dragStart', hidePaneAdder);
this.clojureMacroExpButton.on('click', () => {
const insertPoint =
this.hub.findParentRowOrColumn(this.container.parent) ||
this.container.layoutManager.root.contentItems[0];
insertPoint.addChild(createClojureMacroExpView());
});
createDragSource(this.container.layoutManager, this.gccDumpButton, () => createGccDumpView()).on(
'dragStart',
hidePaneAdder,
@@ -1291,6 +1315,7 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
produceHaskellCore: this.haskellCoreViewOpen,
produceHaskellStg: this.haskellStgViewOpen,
produceHaskellCmm: this.haskellCmmViewOpen,
produceClojureMacroExp: this.clojureMacroExpViewOpen,
overrides: this.getCurrentState().overrides,
},
filters: this.getEffectiveFilters(),
@@ -2186,6 +2211,21 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
}
}
onClojureMacroExpViewOpened(id: number): void {
if (this.id === id) {
this.clojureMacroExpButton.prop('disabled', true);
this.clojureMacroExpViewOpen = true;
this.compile();
}
}
onClojureMacroExpViewClosed(id: number): void {
if (this.id === id) {
this.clojureMacroExpButton.prop('disabled', false);
this.clojureMacroExpViewOpen = false;
}
}
onGccDumpUIInit(id: number): void {
if (this.id === id) {
this.compile();
@@ -2374,6 +2414,7 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
this.haskellCoreButton = this.domRoot.find('.btn.view-haskellCore');
this.haskellStgButton = this.domRoot.find('.btn.view-haskellStg');
this.haskellCmmButton = this.domRoot.find('.btn.view-haskellCmm');
this.clojureMacroExpButton = this.domRoot.find('.btn.view-clojuremacroexp');
this.gccDumpButton = this.domRoot.find('.btn.view-gccdump');
this.cfgButton = this.domRoot.find('.btn.view-cfg');
this.explainButton = this.domRoot.find('.btn.view-explain');
@@ -2657,6 +2698,7 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
this.haskellCmmButton.prop('disabled', this.haskellCmmViewOpen);
this.rustMacroExpButton.prop('disabled', this.rustMacroExpViewOpen);
this.rustHirButton.prop('disabled', this.rustHirViewOpen);
this.clojureMacroExpButton.prop('disabled', this.clojureMacroExpViewOpen);
this.gccDumpButton.prop('disabled', this.gccDumpViewOpen);
this.gnatDebugTreeButton.prop('disabled', this.gnatDebugTreeViewOpen);
this.gnatDebugButton.prop('disabled', this.gnatDebugViewOpen);
@@ -2677,6 +2719,7 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
this.haskellCoreButton.toggle(!!this.compiler.supportsHaskellCoreView);
this.haskellStgButton.toggle(!!this.compiler.supportsHaskellStgView);
this.haskellCmmButton.toggle(!!this.compiler.supportsHaskellCmmView);
this.clojureMacroExpButton.toggle(!!this.compiler.supportsClojureMacroExpView);
// TODO(jeremy-rifkin): Disable cfg button when binary mode is set?
this.cfgButton.toggle(!!this.compiler.supportsCfg);
this.gccDumpButton.toggle(!!this.compiler.supportsGccDump);
@@ -2853,6 +2896,8 @@ export class Compiler extends MonacoPane<monaco.editor.IStandaloneCodeEditor, Co
this.eventHub.on('haskellStgViewClosed', this.onHaskellStgViewClosed, this);
this.eventHub.on('haskellCmmViewOpened', this.onHaskellCmmViewOpened, this);
this.eventHub.on('haskellCmmViewClosed', this.onHaskellCmmViewClosed, this);
this.eventHub.on('clojureMacroExpViewOpened', this.onClojureMacroExpViewOpened, this);
this.eventHub.on('clojureMacroExpViewClosed', this.onClojureMacroExpViewClosed, this);
this.eventHub.on('outputOpened', this.onOutputOpened, this);
this.eventHub.on('outputClosed', this.onOutputClosed, this);

View File

@@ -37,6 +37,7 @@ export enum DiffType {
RustMirOutput = 10,
RustMacroExpOutput = 11,
RustHirOutput = 12,
ClojureMacroExpOutput = 13,
}
export type DiffState = {

View File

@@ -170,6 +170,11 @@ class DiffStateObject {
{text: "<select 'Add new...' → 'Rust HIR' in this compiler's pane>"},
];
break;
case DiffType.ClojureMacroExpOutput:
output = this.result.clojureMacroExpOutput || [
{text: "<select 'Add new...' → 'Clojure Macro Expansion' in this compiler's pane>"},
];
break;
}
}
this.model.setValue(output.map(x => x.text).join('\n'));
@@ -473,6 +478,9 @@ export class Diff extends MonacoPane<monaco.editor.IStandaloneDiffEditor, DiffSt
if (compiler.supportsRustHirView) {
options.push({id: DiffType.RustHirOutput.toString(), name: 'Rust HIR'});
}
if (compiler.supportsClojureMacroExpView) {
options.push({id: DiffType.ClojureMacroExpOutput.toString(), name: 'Clojure Macro Expansion'});
}
}
const lhsoptions = this.getDiffableOptions(this.selectize.lhs, lhsextraoptions);

68
test/clojure-tests.ts Normal file
View File

@@ -0,0 +1,68 @@
// 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 {beforeAll, describe, expect, it} from 'vitest';
import {CompilationEnvironment} from '../lib/compilation-env.js';
import {ClojureCompiler} from '../lib/compilers/index.js';
import {CompilerInfo} from '../types/compiler.interfaces.js';
import {makeCompilationEnvironment} from './utils.js';
const languages = {
clojure: {id: 'clojure'},
};
const info = {
exe: null,
remote: true,
lang: languages.clojure.id,
} as unknown as CompilerInfo;
describe('Basic compiler setup', () => {
let env: CompilationEnvironment;
beforeAll(() => {
env = makeCompilationEnvironment({languages});
});
it('Should not crash on instantiation', () => {
new ClojureCompiler(info, env);
});
describe('Forbidden compiler arguments', () => {
it('ClojureCompiler should not allow a parameter with .clj suffix', () => {
// The ClojureCompiler filters out any option that looks like a source file.
// Other invalid options are filtered out by the wrapper.
const compiler = new ClojureCompiler(info, env);
expect(compiler.filterUserOptions(['hello', 'hello.clj', '-d'])).toEqual(['hello', '-d']);
});
it('ClojureCompiler should not allow user to set --macro-expand parameter', () => {
// --macro-expand is used internally to produce macro expanded output.
const compiler = new ClojureCompiler(info, env);
expect(compiler.filterUserOptions(['--macro-expand', '--something-else'])).toEqual(['--something-else']);
});
});
});

View File

@@ -116,6 +116,7 @@ export type CompilationRequestOptions = {
produceHaskellCore?: boolean;
produceHaskellStg?: boolean;
produceHaskellCmm?: boolean;
produceClojureMacroExp?: boolean;
cmakeArgs?: string;
customOutputFilename?: string;
overrides?: ConfiguredOverrides;
@@ -213,6 +214,8 @@ export type CompilationResult = {
haskellStgOutput?: ResultLine[];
haskellCmmOutput?: ResultLine[];
clojureMacroExpOutput?: ResultLine[];
forceBinaryView?: boolean;
artifacts?: Artifact[];

View File

@@ -90,6 +90,7 @@ export type CompilerInfo = {
supportsHaskellCoreView?: boolean;
supportsHaskellStgView?: boolean;
supportsHaskellCmmView?: boolean;
supportsClojureMacroExpView?: boolean;
supportsCfg?: boolean;
supportsGnatDebugViews?: boolean;
supportsLibraryCodeFilter?: boolean;

View File

@@ -36,6 +36,7 @@ export type LanguageKey =
| 'circle'
| 'circt'
| 'clean'
| 'clojure'
| 'cmake'
| 'cmakescript'
| 'cobol'

View File

@@ -64,6 +64,7 @@ mixin newPaneButton(classId, text, title, icon)
+newPaneButton("view-haskellCore", "GHC Core", "Show GHC Core Intermediate Representation", "fas fa-water")
+newPaneButton("view-haskellStg", "GHC STG", "Show GHC STG Intermediate Representation", "fas fa-water")
+newPaneButton("view-haskellCmm", "GHC Cmm", "Show GHC Cmm Intermediate Representation", "fas fa-water")
+newPaneButton("view-clojuremacroexp", "Clojure Macro Expansion", "Show Clojure macro expansion", "fas fa-arrows-alt")
+newPaneButton("view-gccdump", "GCC Tree/RTL", "Show GCC Tree/RTL dump", "fas fa-tree")
+newPaneButton("view-gnatdebugtree", "GNAT Debug Tree", "Show GNAT debug tree", "fas fa-tree")
+newPaneButton("view-gnatdebug", "GNAT Debug Expanded Code", "Show GNAT debug expanded code", "fas fa-tree")

View File

@@ -85,3 +85,5 @@ mixin monacopane(id)
+monacopane("rustmacroexp")
+monacopane("rusthir")
+monacopane("clojuremacroexp")

View File

@@ -78,6 +78,7 @@ const plugins: Webpack.WebpackPluginInstance[] = [
'scheme',
'objective-c',
'elixir',
'clojure',
],
filename: isDev ? '[name].worker.js' : `[name]${webpackJsHack}worker.[contenthash].js`,
}),