175 lines
6.8 KiB
JavaScript
175 lines
6.8 KiB
JavaScript
import CodeMirror from 'codemirror'
|
|
|
|
// Essentially <noscript>, but also working for ancient web browsers
|
|
// not supporting ES6 used by this script.
|
|
for (const element of document.querySelectorAll('[data-autohide]')) {
|
|
element.style.display = 'none'
|
|
}
|
|
for (const element of document.querySelectorAll('[data-autodisable]')) {
|
|
element.disabled = true
|
|
}
|
|
|
|
const editor = CodeMirror.fromTextArea(document.getElementById('code'), {
|
|
lineNumbers: true,
|
|
matchBrackets: true,
|
|
lineWrapping: true,
|
|
viewportMargin: Infinity,
|
|
minLines: 40,
|
|
})
|
|
const language = document.getElementById('language')
|
|
const futures = new Map()
|
|
function fetchLanguage(id) {
|
|
if (futures.has(id)) {
|
|
return futures.get(id)
|
|
}
|
|
const future = fetch(`/api/v0/language/${id}`).then(x => x.json())
|
|
futures.set(id, future)
|
|
return future
|
|
}
|
|
|
|
const wrapperButtons = document.getElementById('wrapper-buttons')
|
|
const selector = document.createElement('span')
|
|
const compilerOptions = document.createElement('input')
|
|
compilerOptions.placeholder = 'Compiler options'
|
|
compilerOptions.style.display = 'none'
|
|
const implementationButtons = document.createElement('span')
|
|
const buttons = document.createElement('span')
|
|
wrapperButtons.append(selector, compilerOptions, implementationButtons, buttons)
|
|
|
|
const filterAsm = document.createElement('label')
|
|
const filterAsmCheckbox = document.createElement('input')
|
|
filterAsmCheckbox.type = 'checkbox'
|
|
filterAsmCheckbox.checked = true
|
|
filterAsm.append(' ', filterAsmCheckbox, ' Filter assembler directives')
|
|
const filterRegex = /(?:\t\.(?:text|file|section|globl|p2align|type|cfi_.*|size|section)\b|.Lfunc_end).*\n?/g
|
|
|
|
let abortEval = new AbortController
|
|
async function updateLanguage() {
|
|
const initialValue = language.selectedOptions[0].value
|
|
const { mime, mode, sharedWrappers, implementations } = await fetchLanguage(initialValue)
|
|
const isCorrectLanguage = () => initialValue === language.selectedOptions[0].value
|
|
if (isCorrectLanguage()) {
|
|
if (mode) {
|
|
import(`codemirror/mode/${mode}/${mode}.js`).then(() => {
|
|
if (isCorrectLanguage()) {
|
|
editor.setOption('mode', mime)
|
|
}
|
|
})
|
|
}
|
|
implementationButtons.textContent = ''
|
|
selector.textContent = ''
|
|
if (implementations.length) {
|
|
const select = document.createElement('select')
|
|
for (const { label, identifier, wrappers } of implementations) {
|
|
const option = document.createElement('option')
|
|
option.textContent = label
|
|
option.showButtons = () => addButtons(wrappers, implementationButtons, `${initialValue}/${identifier}`)
|
|
select.append(option)
|
|
}
|
|
function updateButtons() {
|
|
select.selectedOptions[0].showButtons()
|
|
}
|
|
select.addEventListener('change', updateButtons)
|
|
updateButtons()
|
|
selector.append(select, '')
|
|
}
|
|
compilerOptions.style.display = implementations.length || sharedWrappers.length ? 'inline' : 'none'
|
|
addButtons(sharedWrappers, buttons, initialValue)
|
|
}
|
|
}
|
|
|
|
function addButtons(wrappers, buttons, prefix) {
|
|
buttons.textContent = ''
|
|
for (const { identifier, label, isAsm, isFormatter } of wrappers) {
|
|
const button = document.createElement('button')
|
|
button.textContent = label
|
|
button.addEventListener('click', async e => {
|
|
e.preventDefault()
|
|
const body = new URLSearchParams
|
|
body.append('compilerOptions', compilerOptions.value)
|
|
body.append('code', editor.getValue())
|
|
abortEval.abort()
|
|
abortEval = new AbortController
|
|
const parameters = {
|
|
method: 'POST',
|
|
body,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
signal: abortEval.signal,
|
|
}
|
|
const output = document.getElementById('output')
|
|
output.textContent = ''
|
|
let response
|
|
try {
|
|
response = await (await fetch(`/api/v0/run/${prefix}/${identifier}`, parameters)).json()
|
|
} catch (e) {
|
|
if (e.name != 'AbortError')
|
|
output.textContent = 'An error occured while running the code. Try again.'
|
|
throw e
|
|
}
|
|
const { status, stdout, stderr } = response
|
|
function updateStdout() {
|
|
stdoutElement.textContent = ''
|
|
if (stdout) {
|
|
if (isAsm && filterAsmCheckbox.checked) {
|
|
stdoutElement.textContent = stdout.replace(filterRegex, "")
|
|
} else {
|
|
stdoutElement.textContent = stdout
|
|
}
|
|
} else {
|
|
const italic = document.createElement('i')
|
|
italic.textContent = '(no output)'
|
|
stdoutElement.append(italic)
|
|
}
|
|
}
|
|
let stdoutElement
|
|
if (stderr) {
|
|
const stderrHeader = document.createElement('h2')
|
|
stderrHeader.textContent = 'Standard error'
|
|
const stderrElement = document.createElement('pre')
|
|
stderrElement.textContent = stderr
|
|
output.append(stderrHeader, stderrElement)
|
|
}
|
|
if (isFormatter) {
|
|
editor.setValue(stdout)
|
|
} else {
|
|
const stdoutHeader = document.createElement('div')
|
|
stdoutHeader.className = 'stdout-header'
|
|
const stdoutHeaderH2 = document.createElement('h2')
|
|
stdoutHeaderH2.textContent = 'Standard output'
|
|
if (status) {
|
|
stdoutHeaderH2.textContent += ` (exit code ${status})`
|
|
}
|
|
stdoutHeader.append(stdoutHeaderH2)
|
|
if (isAsm) {
|
|
stdoutHeader.append(filterAsm)
|
|
filterAsmCheckbox.onchange = updateStdout
|
|
}
|
|
stdoutElement = document.createElement('pre')
|
|
updateStdout()
|
|
output.append(stdoutHeader, stdoutElement)
|
|
}
|
|
})
|
|
buttons.appendChild(button)
|
|
}
|
|
}
|
|
|
|
language.addEventListener('change', updateLanguage)
|
|
updateLanguage()
|
|
|
|
// Restore to the main page state on any changes
|
|
language.addEventListener('change', restoreOriginalState)
|
|
editor.on('change', restoreOriginalState)
|
|
function restoreOriginalState() {
|
|
for (const element of document.querySelectorAll('[data-autohide]')) {
|
|
element.style.display = ''
|
|
}
|
|
for (const element of document.querySelectorAll('[data-autodisable]')) {
|
|
element.disabled = false
|
|
}
|
|
for (const element of document.querySelectorAll('[data-delete-on-modify]')) {
|
|
element.remove()
|
|
}
|
|
}
|