mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 07:04:04 -05:00
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:
6
.github/labeler.yml
vendored
6
.github/labeler.yml
vendored
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'},
|
||||
|
||||
60
etc/config/clojure.amazon.properties
Normal file
60
etc/config/clojure.amazon.properties
Normal 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
|
||||
18
etc/config/clojure.defaults.properties
Normal file
18
etc/config/clojure.defaults.properties
Normal 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
|
||||
141
etc/scripts/clojure_wrapper.clj
Normal file
141
etc/scripts/clojure_wrapper.clj
Normal 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))))
|
||||
5
examples/clojure/default.clj
Normal file
5
examples/clojure/default.clj
Normal file
@@ -0,0 +1,5 @@
|
||||
;; Type your code here, or load an example.
|
||||
(ns example)
|
||||
|
||||
(defn square [num]
|
||||
(* num num))
|
||||
@@ -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>'}, [], []];
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
155
lib/compilers/clojure.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
50
public/logos/clojure.svg
Normal 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 |
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
27
static/panes/clojuremacroexp-view.interfaces.ts
Normal file
27
static/panes/clojuremacroexp-view.interfaces.ts
Normal 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;
|
||||
}
|
||||
129
static/panes/clojuremacroexp-view.ts
Normal file
129
static/panes/clojuremacroexp-view.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export enum DiffType {
|
||||
RustMirOutput = 10,
|
||||
RustMacroExpOutput = 11,
|
||||
RustHirOutput = 12,
|
||||
ClojureMacroExpOutput = 13,
|
||||
}
|
||||
|
||||
export type DiffState = {
|
||||
|
||||
@@ -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
68
test/clojure-tests.ts
Normal 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']);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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[];
|
||||
|
||||
@@ -90,6 +90,7 @@ export type CompilerInfo = {
|
||||
supportsHaskellCoreView?: boolean;
|
||||
supportsHaskellStgView?: boolean;
|
||||
supportsHaskellCmmView?: boolean;
|
||||
supportsClojureMacroExpView?: boolean;
|
||||
supportsCfg?: boolean;
|
||||
supportsGnatDebugViews?: boolean;
|
||||
supportsLibraryCodeFilter?: boolean;
|
||||
|
||||
@@ -36,6 +36,7 @@ export type LanguageKey =
|
||||
| 'circle'
|
||||
| 'circt'
|
||||
| 'clean'
|
||||
| 'clojure'
|
||||
| 'cmake'
|
||||
| 'cmakescript'
|
||||
| 'cobol'
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -85,3 +85,5 @@ mixin monacopane(id)
|
||||
+monacopane("rustmacroexp")
|
||||
|
||||
+monacopane("rusthir")
|
||||
|
||||
+monacopane("clojuremacroexp")
|
||||
|
||||
@@ -78,6 +78,7 @@ const plugins: Webpack.WebpackPluginInstance[] = [
|
||||
'scheme',
|
||||
'objective-c',
|
||||
'elixir',
|
||||
'clojure',
|
||||
],
|
||||
filename: isDev ? '[name].worker.js' : `[name]${webpackJsHack}worker.[contenthash].js`,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user