diff --git a/CHANGELOG.md b/CHANGELOG.md index 8482fe8..76123bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 2.0.0 + +- Ported Comp_iler backend to [Myriad](https://github.com/1Computer1/myriad). +- Removed `exit` command. +- Removed evaluation options. +- Removed Python 2. + ## 1.10.1 - Fixed cleanup not invalidating cache. diff --git a/README.md b/README.md index 8d3072c..70a617f 100644 --- a/README.md +++ b/README.md @@ -42,24 +42,10 @@ int main() ``` ```` -```` ->harmony```js -class Foo { - bar = 1; -} - -console.log(new Foo().bar); -``` -```` - ``` >`py print('hello world')` ``` -``` ->e`hs (+) <$> Just 1 <*> Just 2` -``` - ## Supported Languages and Options One of the following language codes is set in `lang`. @@ -68,81 +54,50 @@ Options are optionally set in `options`, which is a semicolon-delimited list of - `apl` APL - `bash` Bash - `bf` Brainfuck -- `c` C (GCC) +- `c` C - `clj` Clojure -- `cpp` C++ (G++) -- `cs` C# (Mono) - - `e` evaluates a single expression instead of a module +- `cpp` C++ +- `cs` C# - `elixir` Elixir -- `fs` F# (Mono) +- `fs` F# - `go` Go -- `hs` Haskell (GHC) - - `e` evaluates a single expression instead of a module -- `java` Java (OpenJDK) -- `js` JavaScript (Node) - - `harmony` enables harmony features (`--harmony` on node) +- `hs` Haskell +- `java` Java +- `js` JavaScript - `julia` Julia - `lisp` Racket - `lua` Lua - `ocaml` OCaml -- `pas` Pascal (FPC) +- `pas` Pascal - `php` PHP - `pl` Perl5 -- `prolog` Prolog (SWI-Prolog) -- `py` Python (CPython) - - `2` runs Python 2 instead of Python 3 +- `prolog` Prolog +- `py` Python - `rb` Ruby - `rs` Rust ## How it Works -Read the source code, specifically `src/struct/LanguageHandler.js`. -In summary, for every language there is a docker image which spins up a docker container. +For every language there is a docker image which spins up a docker container. The container is used for all evaluations of code, restarting if something goes wrong. The container is locked down, so there is no networking, limited memory and CPU usage, and a time limit. ## Setup -0. Install Docker 18+ -0. Install Node 10+ +0. Install [Docker 18+](https://www.docker.com/) +0. Install [Node 10+](https://nodejs.org/) +0. Install [Myriad](https://github.com/1Computer1/myriad) + - This will require [Stack 2+](https://docs.haskellstack.org/en/stable/README/). + - You will also have to configure Myriad, see its repository. +0. Fill out `config.json` + - `owner` The owner(s) of the bot. Use an array for multiple owners. + - `token` The bot token. + - `prefix` The prefix for commands. + - `codePrefix` The prefix for code evaluation. + - `myriad` The port that Myriad is running on. 0. Run `npm i` -0. Fill out `config.json` as described in the configuration section below + +## Running + +0. Run `myriad --config path/to/config.dhall` 0. Run `node .` - -## Configuration - -### Bot - -- `owner` - The owner(s) of the bot. - Use an array for multiple owners. -- `token` - The bot token. -- `prefix` - The prefix for commands. -- `codePrefix` - The prefix for code evaluation. - -### Setup - -- `languages` Languages to use. - The language names here are different from the user-facing ones. - Check the filenames in `src/languages/` for the language names. - Change to `null` to enable all languages. -- `prepare` Whether to start containers on setup. - Setting to true will speed up the first eval, but that language might not be used. -- `parallel` Whether to build images in parallel. - Will also setup containers in parallel if `prepare` is set. - Faster, but will take more resources. -- `cleanup` Interval in minutes to occasionally kill all containers. - Set to `0` to disable. - -### Compilers - -For each of these options, you can use either the expected value to set it for all compilers or an object with compiler names to the expected values. -If using an object, you can set the default with the `default` key. -The compiler names are the folder names under `docker/`. - -- `memory` Max memory usage of a container. -- `cpu` Max CPU usage of a container. -- `timeout` Time limit for code in milliseconds. -- `concurrent` Number of code evaluations than can run at a time per container. - The more that can run, the more resources a container would need. -- `retries` Maximum number of retries for an evaluation. - Evaluations are retried when all concurrent evaluations fail because one failed. diff --git a/config.example.json b/config.example.json index 25f2fc8..4243ff6 100644 --- a/config.example.json +++ b/config.example.json @@ -3,17 +3,5 @@ "token": "MTU1fdsYNTRb2RT.FcD2l1ig.jIuKqwertyd432RROhF5A", "prefix": ">", "codePrefix": ">", - "languages": [ - "haskell", - "python", - "javascript" - ], - "prepare": false, - "parallel": false, - "cleanup": 60, - "memory": "256m", - "cpus": "0.25", - "timeout": 10000, - "concurrent": 10, - "retries": 2 + "myriad": "8081" } diff --git a/docker/apl/Dockerfile b/docker/apl/Dockerfile deleted file mode 100644 index e756f61..0000000 --- a/docker/apl/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM juergensauermann/gnu-apl -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/apl/run.sh b/docker/apl/run.sh deleted file mode 100644 index 8ac5a27..0000000 --- a/docker/apl/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.apl -apl --OFF -s -f program.apl || true diff --git a/docker/bash/Dockerfile b/docker/bash/Dockerfile deleted file mode 100644 index 91e3a11..0000000 --- a/docker/bash/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM bash -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/bash/run.sh b/docker/bash/run.sh deleted file mode 100644 index 5dbb459..0000000 --- a/docker/bash/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.sh -bash program.sh || true diff --git a/docker/brainfuck/Dockerfile b/docker/brainfuck/Dockerfile deleted file mode 100644 index 067f601..0000000 --- a/docker/brainfuck/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM alpine AS build - -RUN apk update && apk add g++ -COPY bf.cpp . -RUN g++ bf.cpp -o bf - -FROM alpine -LABEL author="1Computer1" - -RUN apk update && apk add libstdc++ -COPY --from=build bf /usr/local/bin/ -COPY run.sh /var/run/ diff --git a/docker/brainfuck/bf.cpp b/docker/brainfuck/bf.cpp deleted file mode 100644 index acb83bc..0000000 --- a/docker/brainfuck/bf.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include -#include -#include -#include - -int main(int argc, char **argv) { - std::string ops; - if (argc == 1) { - std::string line; - while (std::getline(std::cin, line)) { - ops.append(line); - } - - if (ops.empty()) { - std::cerr << "No input given"; - return 1; - } - } else { - ops.assign(argv[1], strlen(argv[1])); - } - - int len = ops.length(); - std::vector tape = { 0 }; - int oix = 0; - int tix = 0; - while (oix < len) { - switch (ops[oix]) { - case '>': - tix++; - if (tix >= tape.size()) { - tape.push_back(0); - } - - oix++; - break; - case '<': - tix--; - if (tix < 0) { - std::cerr << "Out of bounds"; - return 1; - } - - oix++; - break; - case '+': - tape[tix]++; - oix++; - break; - case '-': - tape[tix]--; - oix++; - break; - case '.': - std::cout << tape[tix]; - oix++; - break; - case ',': - std::cin >> tape[tix]; - oix++; - break; - case '[': - if (tape[tix] == 0) { - int ls = 0; - int rs = 0; - for (int i = oix; i < len; i++) { - switch (ops[i]) { - case '[': - ls++; - break; - case ']': - rs++; - break; - default: - break; - } - - if (ls == rs) { - oix = i + 1; - break; - } - } - } else { - oix++; - } - - break; - case ']': - if (tape[tix] != 0) { - int ls = 0; - int rs = 0; - for (int i = oix; i >= 0; i--) { - switch (ops[i]) { - case '[': - ls++; - break; - case ']': - rs++; - break; - default: - break; - } - - if (ls == rs) { - oix = i + 1; - break; - } - } - } else { - oix++; - } - - break; - default: - oix++; - } - } - - return 0; -} diff --git a/docker/brainfuck/run.sh b/docker/brainfuck/run.sh deleted file mode 100644 index b9bdc7a..0000000 --- a/docker/brainfuck/run.sh +++ /dev/null @@ -1 +0,0 @@ -printf %s "$1" | bf || true diff --git a/docker/c/Dockerfile b/docker/c/Dockerfile deleted file mode 100644 index 1324766..0000000 --- a/docker/c/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM alpine -LABEL author="1Computer1" - -RUN apk update -RUN apk add gcc libc-dev - -COPY run.sh /var/run/ diff --git a/docker/c/run.sh b/docker/c/run.sh deleted file mode 100644 index f8f5345..0000000 --- a/docker/c/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.c -gcc program.c -o program && ./program || true diff --git a/docker/clojure/Dockerfile b/docker/clojure/Dockerfile deleted file mode 100644 index d1feee2..0000000 --- a/docker/clojure/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM clojure:tools-deps-alpine -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/clojure/run.sh b/docker/clojure/run.sh deleted file mode 100644 index dc5b764..0000000 --- a/docker/clojure/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.clj -clojure program.clj || true diff --git a/docker/cpp/Dockerfile b/docker/cpp/Dockerfile deleted file mode 100644 index 37bc37b..0000000 --- a/docker/cpp/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM alpine -LABEL author="1Computer1" - -RUN apk update -RUN apk add g++ - -COPY run.sh /var/run/ diff --git a/docker/cpp/run.sh b/docker/cpp/run.sh deleted file mode 100644 index d9e2f85..0000000 --- a/docker/cpp/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.cpp -g++ program.cpp -o program && ./program || true diff --git a/docker/csharp/Dockerfile b/docker/csharp/Dockerfile deleted file mode 100644 index 28584bb..0000000 --- a/docker/csharp/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM mono -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/csharp/run.sh b/docker/csharp/run.sh deleted file mode 100644 index dcf5f79..0000000 --- a/docker/csharp/run.sh +++ /dev/null @@ -1,6 +0,0 @@ -if [ "$EVAL_EXPR" = "true" ]; then - printf %s "$1" | csharp -e -else - printf %s "$1" > program.cs - csc program.cs >/dev/null && mono program.exe || true -fi diff --git a/docker/elixir/Dockerfile b/docker/elixir/Dockerfile deleted file mode 100644 index 7f7bbd8..0000000 --- a/docker/elixir/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM elixir:alpine -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/elixir/run.sh b/docker/elixir/run.sh deleted file mode 100644 index ab64413..0000000 --- a/docker/elixir/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.exs -elixir program.exs || true diff --git a/docker/fsharp/Dockerfile b/docker/fsharp/Dockerfile deleted file mode 100644 index 15f6c2c..0000000 --- a/docker/fsharp/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM fsharp -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/fsharp/run.sh b/docker/fsharp/run.sh deleted file mode 100644 index 2a0d0a2..0000000 --- a/docker/fsharp/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.fs -fsharpc --optimize- program.fs >/dev/null && mono program.exe || true diff --git a/docker/go/Dockerfile b/docker/go/Dockerfile deleted file mode 100644 index 2dddf77..0000000 --- a/docker/go/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM golang:alpine -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/go/run.sh b/docker/go/run.sh deleted file mode 100644 index 9f4c157..0000000 --- a/docker/go/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -export GOCACHE=/tmp/"$CODEDIR"/cache -printf %s "$1" > program.go -go run program.go || true diff --git a/docker/haskell/.ghci b/docker/haskell/.ghci deleted file mode 100644 index 009d2a5..0000000 --- a/docker/haskell/.ghci +++ /dev/null @@ -1,18 +0,0 @@ -import Data.Bool -import Data.Char -import Data.Either -import Data.Function -import Data.List -import Data.Maybe -import Data.Ord -import Control.Applicative -import Control.Monad - -import Data.Map (Map) -import qualified Data.Map as Map -import Data.Set (Set) -import qualified Data.Set as Set -import Data.Sequence (Seq) -import qualified Data.Sequence as Seq -import Data.Tree (Tree) -import qualified Data.Tree as Tree diff --git a/docker/haskell/Dockerfile b/docker/haskell/Dockerfile deleted file mode 100644 index a9678b0..0000000 --- a/docker/haskell/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM debian:stretch -LABEL author="1Computer1" -ENV LANG C.UTF-8 - -RUN apt-get update && \ - apt-get install -y --no-install-recommends gnupg dirmngr && \ - echo 'deb http://downloads.haskell.org/debian stretch main' > /etc/apt/sources.list.d/ghc.list && \ - apt-key adv --keyserver keyserver.ubuntu.com --recv-keys BA3CBA3FFE22B574 && \ - apt-get update && \ - apt-get install -y --no-install-recommends ghc-8.6.5 - -ENV PATH /opt/ghc/8.6.5/bin:$PATH - -COPY .ghci $HOME/ -COPY run.sh /var/run/ diff --git a/docker/haskell/run.sh b/docker/haskell/run.sh deleted file mode 100644 index 690c29e..0000000 --- a/docker/haskell/run.sh +++ /dev/null @@ -1,6 +0,0 @@ -if [ "$EVAL_EXPR" = "true" ]; then - ghc -e "$1" || true -else - printf %s "$1" > program.hs - ghc -e main program.hs || true -fi diff --git a/docker/java/Dockerfile b/docker/java/Dockerfile deleted file mode 100644 index dcc0975..0000000 --- a/docker/java/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM openjdk:13-alpine -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/java/run.sh b/docker/java/run.sh deleted file mode 100644 index db8e8ce..0000000 --- a/docker/java/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > Main.java -javac Main.java && java Main || true diff --git a/docker/javascript/Dockerfile b/docker/javascript/Dockerfile deleted file mode 100644 index a3e499a..0000000 --- a/docker/javascript/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM node:alpine -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/javascript/run.sh b/docker/javascript/run.sh deleted file mode 100644 index c82d76d..0000000 --- a/docker/javascript/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -if [ "$EVAL_HARMONY" = "true" ]; then - printf %s "$1" | node --harmony -p || true -else - printf %s "$1" | node -p || true -fi diff --git a/docker/julia/Dockerfile b/docker/julia/Dockerfile deleted file mode 100644 index b81a6c8..0000000 --- a/docker/julia/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM julia -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/julia/run.sh b/docker/julia/run.sh deleted file mode 100644 index 7cf9563..0000000 --- a/docker/julia/run.sh +++ /dev/null @@ -1 +0,0 @@ -printf %s "$1" | julia diff --git a/docker/lua/Dockerfile b/docker/lua/Dockerfile deleted file mode 100644 index 0835675..0000000 --- a/docker/lua/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM alpine - -RUN apk update -RUN apk add lua5.3 - -COPY run.sh /var/run/ diff --git a/docker/lua/run.sh b/docker/lua/run.sh deleted file mode 100644 index f229802..0000000 --- a/docker/lua/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.lua -lua5.3 program.lua || true diff --git a/docker/ocaml/Dockerfile b/docker/ocaml/Dockerfile deleted file mode 100644 index e332bb1..0000000 --- a/docker/ocaml/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM frolvlad/alpine-ocaml -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/ocaml/run.sh b/docker/ocaml/run.sh deleted file mode 100644 index cdaaa9a..0000000 --- a/docker/ocaml/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.ml -ocamlopt -cclib --static -o program program.ml && ./program || true diff --git a/docker/pascal/Dockerfile b/docker/pascal/Dockerfile deleted file mode 100644 index 981c1aa..0000000 --- a/docker/pascal/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM frolvlad/alpine-fpc -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/pascal/run.sh b/docker/pascal/run.sh deleted file mode 100644 index 99afe11..0000000 --- a/docker/pascal/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -printf %s "$1" > program.pas - -# fpc does not use stderr, ld however does, capture both -res="$(fpc program.pas 2>&1)" - -if [ $? -eq 0 ]; then - ./program || true -else - printf %s "$res" -fi diff --git a/docker/perl/Dockerfile b/docker/perl/Dockerfile deleted file mode 100644 index 2826c4e..0000000 --- a/docker/perl/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM perl:slim -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/perl/run.sh b/docker/perl/run.sh deleted file mode 100644 index 3ba6d9e..0000000 --- a/docker/perl/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.pl -perl program.pl || true diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile deleted file mode 100644 index 980d994..0000000 --- a/docker/php/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM php:alpine -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/php/run.sh b/docker/php/run.sh deleted file mode 100644 index aeac323..0000000 --- a/docker/php/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.php -php program.php || true diff --git a/docker/prolog/Dockerfile b/docker/prolog/Dockerfile deleted file mode 100644 index 1d0f232..0000000 --- a/docker/prolog/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM swipl -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/prolog/run.sh b/docker/prolog/run.sh deleted file mode 100644 index 81cbdee..0000000 --- a/docker/prolog/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.pl -swipl --quiet program.pl || true diff --git a/docker/python2/Dockerfile b/docker/python2/Dockerfile deleted file mode 100644 index a24884a..0000000 --- a/docker/python2/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM python:2-alpine -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/python2/run.sh b/docker/python2/run.sh deleted file mode 100644 index 480954f..0000000 --- a/docker/python2/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.py -python program.py || true diff --git a/docker/python3/Dockerfile b/docker/python3/Dockerfile deleted file mode 100644 index 698a384..0000000 --- a/docker/python3/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM python:3-alpine -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/python3/run.sh b/docker/python3/run.sh deleted file mode 100644 index 480954f..0000000 --- a/docker/python3/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.py -python program.py || true diff --git a/docker/racket/Dockerfile b/docker/racket/Dockerfile deleted file mode 100644 index 4e26694..0000000 --- a/docker/racket/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM jackfirth/racket -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/racket/run.sh b/docker/racket/run.sh deleted file mode 100644 index 0b89642..0000000 --- a/docker/racket/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.rkt -racket program.rkt || true diff --git a/docker/ruby/Dockerfile b/docker/ruby/Dockerfile deleted file mode 100644 index f0bdb21..0000000 --- a/docker/ruby/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM ruby:alpine -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/ruby/run.sh b/docker/ruby/run.sh deleted file mode 100644 index 688c3b0..0000000 --- a/docker/ruby/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.rb -ruby program.rb || true diff --git a/docker/rust/Dockerfile b/docker/rust/Dockerfile deleted file mode 100644 index 93bf3e3..0000000 --- a/docker/rust/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM rust:slim -LABEL author="1Computer1" - -COPY run.sh /var/run/ diff --git a/docker/rust/run.sh b/docker/rust/run.sh deleted file mode 100644 index b150608..0000000 --- a/docker/rust/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -printf %s "$1" > program.rs -rustc -C opt-level=0 --color never program.rs && ./program || true diff --git a/package.json b/package.json index 1272c55..4441d18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "comp_iler", - "version": "1.10.1", + "version": "2.0.0", "description": "Sandboxed eval bot", "main": "src/index.js", "author": "1Computer1", diff --git a/src/commands/about.js b/src/commands/about.js index 095bb49..e30761e 100644 --- a/src/commands/about.js +++ b/src/commands/about.js @@ -14,6 +14,9 @@ class AboutCommand extends Command { 'Comp_iler is made by 1Computer.', 'Source code is available at .', '', + 'Comp_iler runs on Myriad, a Docker-based arbitrary code evaluation server!', + 'Check it out here .', + '', `**Guilds**: ${this.client.guilds.size}`, `**Channels**: ${this.client.channels.size}`, `**Users**: ${this.client.users.size}`, diff --git a/src/commands/cleanup.js b/src/commands/cleanup.js index 84e47d5..3d361e5 100644 --- a/src/commands/cleanup.js +++ b/src/commands/cleanup.js @@ -11,7 +11,7 @@ class CleanupCommand extends Command { async exec(message) { await message.util.send('Cleaning up...'); - await this.client.languageHandler.cleanup(); + await this.client.myriad.postCleanup(); await message.util.send('Cleaned up!'); } } diff --git a/src/commands/exit.js b/src/commands/exit.js deleted file mode 100644 index 174837d..0000000 --- a/src/commands/exit.js +++ /dev/null @@ -1,20 +0,0 @@ -const { Command } = require('discord-akairo'); - -class ExitCommand extends Command { - constructor() { - super('exit', { - aliases: ['exit'], - ownerOnly: true, - clientPermissions: ['SEND_MESSAGES'] - }); - } - - async exec(message) { - await message.util.send('Cleaning up...'); - await this.client.languageHandler.cleanup(); - await message.util.send('Exiting!'); - process.exit(); - } -} - -module.exports = ExitCommand; diff --git a/src/commands/help.js b/src/commands/help.js index abbc2b2..c10854f 100644 --- a/src/commands/help.js +++ b/src/commands/help.js @@ -13,11 +13,9 @@ class HelpCommand extends Command { return message.util.send([ '**Usage:**', `Put a \`${codePrefix}\` before a code block or inline codeblock that starts with a language code to execute it.`, - `You can add options, separated by semicolons, after the \`${codePrefix}\`.`, - '', `For list of enabled languages, use the \`${prefix}languages\` command.`, '', - 'See the readme for usage examples, supported languages, and options: ' + 'See the readme for usage examples and supported languages: ' ]); } } diff --git a/src/commands/languages.js b/src/commands/languages.js index 886c55a..ba929f9 100644 --- a/src/commands/languages.js +++ b/src/commands/languages.js @@ -1,19 +1,21 @@ const { Command } = require('discord-akairo'); +const Myriad = require('../struct/Myriad'); class LanguagesCommand extends Command { constructor() { super('languages', { - aliases: ['languages', 'language'], + aliases: ['languages', 'language', 'langs', 'lang'], clientPermissions: ['SEND_MESSAGES'] }); } - exec(message) { + async exec(message) { + const languages = await this.client.myriad.getLanguages(); return message.util.send([ - '**List of enabled languages (Name: Language Codes)**:', - ...this.client.languageHandler.modules.map(lang => `${lang.name}: \`${lang.aliases.join('`, `')}\``), + '**List of enabled languages (Language Codes)**:', + ...languages.map(lang => `\`${Myriad.Languages.get(lang).join('`, `')}\``), '', - 'See the readme for usage examples, supported languages, and options: ' + 'See the readme for usage examples: ' ]); } } diff --git a/src/languages/apl.js b/src/languages/apl.js deleted file mode 100644 index 680bc49..0000000 --- a/src/languages/apl.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class APL extends Language { - constructor() { - super('apl', { - name: 'APL', - aliases: ['apl'] - }); - } -} - -module.exports = APL; diff --git a/src/languages/bash.js b/src/languages/bash.js deleted file mode 100644 index 65b5671..0000000 --- a/src/languages/bash.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Bash extends Language { - constructor() { - super('bash', { - name: 'Bash', - aliases: ['bash', 'sh'] - }); - } -} - -module.exports = Bash; diff --git a/src/languages/brainfuck.js b/src/languages/brainfuck.js deleted file mode 100644 index d8e7b55..0000000 --- a/src/languages/brainfuck.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Brainfuck extends Language { - constructor() { - super('brainfuck', { - name: 'Brainfuck', - aliases: ['brainfuck', 'bf'] - }); - } -} - -module.exports = Brainfuck; diff --git a/src/languages/c.js b/src/languages/c.js deleted file mode 100644 index a773bd5..0000000 --- a/src/languages/c.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class C extends Language { - constructor() { - super('c', { - name: 'C', - aliases: ['c'] - }); - } -} - -module.exports = C; diff --git a/src/languages/clojure.js b/src/languages/clojure.js deleted file mode 100644 index 4fd13c7..0000000 --- a/src/languages/clojure.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Clojure extends Language { - constructor() { - super('clojure', { - name: 'Clojure', - aliases: ['clojure', 'clj'] - }); - } -} - -module.exports = Clojure; diff --git a/src/languages/cpp.js b/src/languages/cpp.js deleted file mode 100644 index b11cda6..0000000 --- a/src/languages/cpp.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class CPP extends Language { - constructor() { - super('cpp', { - name: 'C++', - aliases: ['cpp', 'c++'] - }); - } -} - -module.exports = CPP; diff --git a/src/languages/csharp.js b/src/languages/csharp.js deleted file mode 100644 index 0ca2c24..0000000 --- a/src/languages/csharp.js +++ /dev/null @@ -1,23 +0,0 @@ -const Language = require('../struct/Language'); - -class CSharp extends Language { - constructor() { - super('csharp', { - name: 'C#', - aliases: ['csharp', 'cs'], - options: { - e: () => '' - } - }); - } - - runWith(options) { - if (options.has('e')) { - return { env: { EVAL_EXPR: 'true' } }; - } - - return {}; - } -} - -module.exports = CSharp; diff --git a/src/languages/elixir.js b/src/languages/elixir.js deleted file mode 100644 index e0b1cb1..0000000 --- a/src/languages/elixir.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Elixir extends Language { - constructor() { - super('elixir', { - name: 'Elixir', - aliases: ['elixir'] - }); - } -} - -module.exports = Elixir; diff --git a/src/languages/fsharp.js b/src/languages/fsharp.js deleted file mode 100644 index 89b939f..0000000 --- a/src/languages/fsharp.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class FSharp extends Language { - constructor() { - super('fsharp', { - name: 'F#', - aliases: ['fsharp', 'fs'] - }); - } -} - -module.exports = FSharp; diff --git a/src/languages/go.js b/src/languages/go.js deleted file mode 100644 index 9a487a3..0000000 --- a/src/languages/go.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Go extends Language { - constructor() { - super('go', { - name: 'Go', - aliases: ['golang', 'go'] - }); - } -} - -module.exports = Go; diff --git a/src/languages/haskell.js b/src/languages/haskell.js deleted file mode 100644 index 193c311..0000000 --- a/src/languages/haskell.js +++ /dev/null @@ -1,23 +0,0 @@ -const Language = require('../struct/Language'); - -class Haskell extends Language { - constructor() { - super('haskell', { - name: 'Haskell', - aliases: ['haskell', 'hs'], - options: { - e: () => '' - } - }); - } - - runWith(options) { - if (options.has('e')) { - return { env: { EVAL_EXPR: 'true' } }; - } - - return {}; - } -} - -module.exports = Haskell; diff --git a/src/languages/java.js b/src/languages/java.js deleted file mode 100644 index 27848e3..0000000 --- a/src/languages/java.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Java extends Language { - constructor() { - super('java', { - name: 'Java', - aliases: ['java'] - }); - } -} - -module.exports = Java; diff --git a/src/languages/javascript.js b/src/languages/javascript.js deleted file mode 100644 index e71259a..0000000 --- a/src/languages/javascript.js +++ /dev/null @@ -1,24 +0,0 @@ -const Language = require('../struct/Language'); - -class JavaScript extends Language { - constructor() { - super('javascript', { - name: 'JavaScript', - aliases: ['javascript', 'js'], - options: { - harmony: () => '' - } - }); - } - - runWith(options) { - const ret = { id: this.id, env: {} }; - if (options.has('harmony')) { - ret.env.EVAL_HARMONY = 'true'; - } - - return ret; - } -} - -module.exports = JavaScript; diff --git a/src/languages/julia.js b/src/languages/julia.js deleted file mode 100644 index 31e8ddc..0000000 --- a/src/languages/julia.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Julia extends Language { - constructor() { - super('julia', { - name: 'Julia', - aliases: ['julia'] - }); - } -} - -module.exports = Julia; diff --git a/src/languages/lua.js b/src/languages/lua.js deleted file mode 100644 index 62eae16..0000000 --- a/src/languages/lua.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Lua extends Language { - constructor() { - super('lua', { - name: 'Lua', - aliases: ['lua'] - }); - } -} - -module.exports = Lua; diff --git a/src/languages/ocaml.js b/src/languages/ocaml.js deleted file mode 100644 index 01adf12..0000000 --- a/src/languages/ocaml.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class OCaml extends Language { - constructor() { - super('ocaml', { - name: 'OCaml', - aliases: ['ocaml'] - }); - } -} - -module.exports = OCaml; diff --git a/src/languages/pascal.js b/src/languages/pascal.js deleted file mode 100644 index 6ac387c..0000000 --- a/src/languages/pascal.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Pascal extends Language { - constructor() { - super('pascal', { - name: 'Pascal', - aliases: ['pascal', 'pas', 'freepascal'] - }); - } -} - -module.exports = Pascal; diff --git a/src/languages/perl.js b/src/languages/perl.js deleted file mode 100644 index ad25559..0000000 --- a/src/languages/perl.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Perl extends Language { - constructor() { - super('perl', { - name: 'Perl', - aliases: ['perl', 'pl'] - }); - } -} - -module.exports = Perl; diff --git a/src/languages/php.js b/src/languages/php.js deleted file mode 100644 index f1c446f..0000000 --- a/src/languages/php.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class PHP extends Language { - constructor() { - super('php', { - name: 'PHP', - aliases: ['php'] - }); - } -} - -module.exports = PHP; diff --git a/src/languages/prolog.js b/src/languages/prolog.js deleted file mode 100644 index 6858e3e..0000000 --- a/src/languages/prolog.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Prolog extends Language { - constructor() { - super('prolog', { - name: 'Prolog', - aliases: ['prolog'] - }); - } -} - -module.exports = Prolog; diff --git a/src/languages/python.js b/src/languages/python.js deleted file mode 100644 index 3ad3d56..0000000 --- a/src/languages/python.js +++ /dev/null @@ -1,24 +0,0 @@ -const Language = require('../struct/Language'); - -class Python extends Language { - constructor() { - super('python', { - name: 'Python', - aliases: ['python', 'py'], - loads: ['python3', 'python2'], - options: { - 2: () => '' - } - }); - } - - runWith(options) { - if (options.has('2')) { - return { id: 'python2' }; - } - - return { id: 'python3' }; - } -} - -module.exports = Python; diff --git a/src/languages/racket.js b/src/languages/racket.js deleted file mode 100644 index 2394c54..0000000 --- a/src/languages/racket.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Racket extends Language { - constructor() { - super('racket', { - name: 'Racket', - aliases: ['lisp'] - }); - } -} - -module.exports = Racket; diff --git a/src/languages/ruby.js b/src/languages/ruby.js deleted file mode 100644 index 5a879a2..0000000 --- a/src/languages/ruby.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Ruby extends Language { - constructor() { - super('ruby', { - name: 'Ruby', - aliases: ['ruby', 'rb'] - }); - } -} - -module.exports = Ruby; diff --git a/src/languages/rust.js b/src/languages/rust.js deleted file mode 100644 index 3a88418..0000000 --- a/src/languages/rust.js +++ /dev/null @@ -1,12 +0,0 @@ -const Language = require('../struct/Language'); - -class Rust extends Language { - constructor() { - super('rust', { - name: 'Rust', - aliases: ['rust', 'rs'] - }); - } -} - -module.exports = Rust; diff --git a/src/listeners/messageInvalid.js b/src/listeners/messageInvalid.js index a2f3916..22c991c 100644 --- a/src/listeners/messageInvalid.js +++ b/src/listeners/messageInvalid.js @@ -1,4 +1,5 @@ const { Listener } = require('discord-akairo'); +const Myriad = require('../struct/Myriad'); const fetch = require('node-fetch'); class MessageInvalidListener extends Listener { @@ -26,95 +27,51 @@ class MessageInvalidListener extends Listener { reaction = await message.react('📝'); } - let errored = false; - let result; - try { - ({ output: result, hasStderr: errored } = await this.client.languageHandler.evalCode(parse)); - } catch (e) { - errored = true; - result = e.message; - } - - result = result || ''; + const [ok, response] = await this.client.myriad.postEval(parse.language, parse.code); if (!message.guild || message.channel.permissionsFor(this.client.user).has('ADD_REACTIONS')) { if (reaction) { reaction.users.remove(); } - if (errored) { - message.react('✖'); - } else { + if (ok) { message.react('✔'); + } else { + message.react('✖'); } } - const invalid = parse.invalid.length ? `Invalid options: ${parse.invalid.join(', ')}\n` : ''; - const output = `${invalid}\`\`\`\n${result}\n\`\`\``; + const output = `\`\`\`\n${response}\n\`\`\``; if (output.length >= 2000) { - const key = await fetch('https://hastebin.com/documents', { method: 'POST', body: result }) + const key = await fetch('https://hastebin.com/documents', { method: 'POST', body: response }) .then(res => res.json()) .then(json => json.key); - return message.util.send(`${invalid}Output was too long: `); + return message.util.send(`Output was too long: `); } return message.util.send(output); } parseMessage(message) { - const prefix = this.client.config.codePrefix.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); - const regex1 = new RegExp(`^\\s*${prefix}\\s*(.+?)?\\s*\`\`\`(.+?)\\n([^]+)\`\`\`\\s*$`); - const regex2 = new RegExp(`^\\s*${prefix}\\s*(.+?)?\\s*\`(.+?) \\s*([^]+)\`\\s*$`); - const match = message.content.match(regex1) || message.content.match(regex2); + const prefix = this.client.config.codePrefix; + const starts = message.content.slice(0, prefix.length).toLowerCase().startsWith(prefix.toLowerCase()); + if (!starts) { + return null; + } + + const regex = /^\s*(`{1,3})(.+?)\n([^]+)\1\s*$/; + const match = message.content.slice(prefix.length).match(regex); if (!match) { return null; } - const language = this.parseLanguage(match[2]); + const language = Myriad.Aliases.get(match[2].toLowerCase()); if (!language) { return null; } const code = match[3].trim(); - const [valid, invalid] = this.parseOptions(language, match[1] || ''); - return { id: message.id, language, code, options: valid, invalid }; - } - - parseLanguage(language) { - const match = this.client.languageHandler.findLanguage(language.trim()); - if (!match) { - return null; - } - - return match; - } - - parseOptions(language, options) { - const kvs = options.split(';').map(opt => { - const [k, v = ''] = opt.split('='); - return [k.toLowerCase().trim(), v.trim()]; - }); - - const valid = new Map(); - const invalid = []; - for (const [key, value] of kvs) { - if (!key) { - continue; - } - - if (Object.prototype.hasOwnProperty.call(language.options, key)) { - const parse = language.options[key](value); - if (parse != null) { - valid.set(key, parse); - } else { - invalid.push(key); - } - } else { - invalid.push(key); - } - } - - return [valid, invalid]; + return { language, code }; } } diff --git a/src/struct/CompilerClient.js b/src/struct/CompilerClient.js index ed4a16b..a343f4e 100644 --- a/src/struct/CompilerClient.js +++ b/src/struct/CompilerClient.js @@ -1,5 +1,5 @@ const { AkairoClient, CommandHandler, ListenerHandler } = require('discord-akairo'); -const LanguageHandler = require('./LanguageHandler'); +const Myriad = require('./Myriad'); const path = require('path'); class CompilerClient extends AkairoClient { @@ -24,25 +24,19 @@ class CompilerClient extends AkairoClient { directory: path.join(__dirname, '../listeners') }); - this.languageHandler = new LanguageHandler(this, { - directory: path.join(__dirname, '../languages') - }); - + this.myriad = new Myriad(config.myriad); this.config = config; } - async start() { + start() { this.commandHandler.useListenerHandler(this.listenerHandler); this.listenerHandler.setEmitters({ commandHandler: this.commandHandler, - listenerHandler: this.listenerHandler, - languageHandler: this.languageHandler + listenerHandler: this.listenerHandler }); this.commandHandler.loadAll(); this.listenerHandler.loadAll(); - this.languageHandler.loadAll(); - await this.languageHandler.buildDocker(); return this.login(this.config.token); } } diff --git a/src/struct/Language.js b/src/struct/Language.js deleted file mode 100644 index f33c819..0000000 --- a/src/struct/Language.js +++ /dev/null @@ -1,24 +0,0 @@ -const { AkairoModule } = require('discord-akairo'); - -class Language extends AkairoModule { - constructor(id, { - category, - name, - aliases, - loads = [id], - options = {} - } = {}) { - super(id, { category }); - - this.name = name; - this.aliases = aliases; - this.loads = loads; - this.options = options; - } - - runWith() { - return {}; - } -} - -module.exports = Language; diff --git a/src/struct/LanguageHandler.js b/src/struct/LanguageHandler.js deleted file mode 100644 index e1ec519..0000000 --- a/src/struct/LanguageHandler.js +++ /dev/null @@ -1,227 +0,0 @@ -const { AkairoHandler } = require('discord-akairo'); -const { Collection } = require('discord.js'); -const Language = require('./Language'); -const Queue = require('./Queue'); -const childProcess = require('child_process'); -const util = require('util'); -const path = require('path'); - -const exec = util.promisify(childProcess.exec); - -class LanguageHandler extends AkairoHandler { - constructor(client, { - directory, - classToHandle = Language, - extensions = ['.js', '.ts'], - automateCategories, - loadFilter = filepath => - !this.client.config.languages || this.client.config.languages.includes(path.parse(filepath).name) - }) { - super(client, { - directory, - classToHandle, - extensions, - automateCategories, - loadFilter - }); - - this.aliases = new Collection(); - this.containers = new Collection(); - this.queues = new Collection(); - } - - register(language, filepath) { - super.register(language, filepath); - - for (let alias of language.aliases) { - const conflict = this.aliases.get(alias.toLowerCase()); - if (conflict) { - throw new TypeError(`Alias conflict of ${alias} between ${language.id} and ${conflict}`); - } - - alias = alias.toLowerCase(); - this.aliases.set(alias, language.id); - } - } - - deregister(language) { - for (let alias of language.aliases) { - alias = alias.toLowerCase(); - this.aliases.delete(alias); - } - - super.deregister(language); - } - - findLanguage(alias) { - return this.modules.get(this.aliases.get(alias.toLowerCase())); - } - - async buildDocker() { - if (this.client.config.parallel) { - await Promise.all(this.modules.map(({ loads }) => Promise.all(loads.map(dockerID => this.buildImage(dockerID))))); - return; - } - - for (const { loads } of this.modules.values()) { - for (const dockerID of loads) { - // eslint-disable-next-line no-await-in-loop - await this.buildImage(dockerID); - } - } - - if (this.client.config.cleanup > 0) { - setInterval(() => this.cleanup().catch(() => null), this.client.config.cleanup * 60 * 1000); - } - } - - async buildImage(dockerID) { - const folder = path.join(__dirname, '../../docker', dockerID); - await util.promisify(childProcess.exec)(`docker build -t "1computer1/comp_iler:${dockerID}" ${folder}`); - // eslint-disable-next-line no-console - console.log(`Built image 1computer1/comp_iler:${dockerID}.`); - const concurrent = this.getCompilerConfig(dockerID, 'concurrent', 'number'); - this.queues.set(dockerID, { evalQueue: new Queue(concurrent), setupQueue: new Queue(1) }); - if (this.client.config.prepare) { - await this.setupContainer(dockerID); - } - } - - async setupContainer(dockerID) { - if (this.containers.has(dockerID)) { - return this.containers.get(dockerID); - } - - const cpus = this.getCompilerConfig(dockerID, 'cpus', 'string'); - const memory = this.getCompilerConfig(dockerID, 'memory', 'string'); - const name = `comp_iler-${dockerID}-${Date.now()}`; - try { - await exec([ - `docker run --rm --name=${name}`, - '-u1000:1000 -w/tmp/ -dt', - `--net=none --cpus=${cpus} -m=${memory} --memory-swap=${memory}`, - `1computer1/comp_iler:${dockerID} /bin/sh` - ].join(' ')); - - await exec(`docker exec ${name} mkdir eval`); - await exec(`docker exec ${name} chmod 711 eval`); - this.containers.set(dockerID, { name }); - // eslint-disable-next-line no-console - console.log(`Started container ${name}.`); - return this.containers.get(dockerID); - } catch (err) { - throw err; - } - } - - evalCode({ language, code, options, retries = 0 }) { - const { id: dockerID = language.id, env = {} } = language.runWith(options); - const { evalQueue, setupQueue } = this.queues.get(dockerID); - return evalQueue.enqueue(async () => { - const { name } = await setupQueue.enqueue(() => this.setupContainer(dockerID)); - const now = Date.now(); - try { - await exec(`docker exec ${name} mkdir eval/${now}`); - await exec(`docker exec ${name} chmod 777 eval/${now}`); - const proc = childProcess.spawn('docker', [ - 'exec', '-u1001:1001', `-w/tmp/eval/${now}`, - ...Object.entries(env).map(([k, v]) => `-e${k}=${v}`), - name, '/bin/sh', '/var/run/run.sh', code - ]); - - const timeout = this.getCompilerConfig(dockerID, 'timeout', 'number'); - const result = await this.handleSpawn(proc, timeout); - await exec(`docker exec ${name} rm -rf eval/${now}`); - return result; - } catch (err) { - this.containers.delete(dockerID); - try { - await this.kill(name); - } catch (err2) { - // The container was not alive to be killed, i.e. multiple evals were occuring, - // one of them caused the container to be killed, the remaining evals all fail. - // Retry those remaining ones until they work! - if (retries < this.getCompilerConfig(dockerID, 'retries', 'number')) { - return this.evalCode({ language, code, options, retries: retries + 1 }); - } - } - - throw err; - } - }); - } - - handleSpawn(proc, timeout = null) { - return new Promise((resolve, reject) => { - let handled = false; - if (timeout !== null) { - setTimeout(() => { - handled = true; - reject(new Error('Timed out')); - }, timeout); - } - - let output = ''; - let hasStderr = false; - let error; - proc.stdout.on('data', chunk => { - output += chunk; - }); - - proc.stderr.on('data', chunk => { - hasStderr = true; - output += chunk; - }); - - proc.on('error', e => { - error = e; - }); - - proc.on('close', status => { - if (!handled) { - handled = true; - if (status !== 0 || error) { - if (!error) { - error = new Error(output || 'Something went wrong'); - } - - reject(error); - } else { - resolve({ output, hasStderr }); - } - } - }); - }); - } - - async kill(name) { - let cmd; - if (process.platform === 'win32') { - cmd = `docker kill --signal=9 ${name} >nul 2>nul`; - } else { - cmd = `docker kill --signal=9 ${name} >/dev/null 2>/dev/null`; - } - - await exec(cmd); - // eslint-disable-next-line no-console - console.log(`Killed container ${name}.`); - } - - cleanup() { - return Promise.all(this.containers.map(({ name }, dockerID) => { - this.containers.delete(dockerID); - return this.kill(name); - })); - } - - getCompilerConfig(dockerID, key, type) { - const o = this.client.config[key]; - return typeof o === type - ? o - : o[dockerID] !== undefined - ? o[dockerID] - : o.default; - } -} - -module.exports = LanguageHandler; diff --git a/src/struct/Myriad.js b/src/struct/Myriad.js new file mode 100644 index 0000000..efafe8b --- /dev/null +++ b/src/struct/Myriad.js @@ -0,0 +1,87 @@ +const fetch = require('node-fetch'); + +class Myriad { + constructor(port) { + this.port = port; + } + + url(k) { + return `http://localhost:${this.port}/${k}`; + } + + getLanguages() { + return fetch(this.url('languages'), { method: 'GET' }).then(x => x.json()); + } + + async postEval(language, code) { + const response = await fetch(this.url('eval'), { + method: 'POST', + body: JSON.stringify({ language, code }), + headers: { 'Content-Type': 'application/json' } + }); + + if (!response.ok) { + if (response.status === 404 && /^Language .+? was not found$/.test(response.statusText)) { + return [false, 'Invalid language']; + } + + if (response.status === 500 && response.statusText === 'Evaluation failed') { + return [false, 'Evaluation failed']; + } + + if (response.status === 504 && response.statusText === 'Evaluation timed out') { + return [false, 'Evaluation timed out']; + } + + throw new Error(`Unexpected ${response.status} response from ['Myriad', ${response.statusText}`); + } + + const body = await response.json(); + return [true, body.result || '\n']; + } + + getContainers() { + return fetch(this.url('containers'), { method: 'GET' }).then(x => x.json()); + } + + postCleanup() { + return fetch(this.url('cleanup'), { method: 'POST' }).then(x => x.json()); + } +} + +const entries = [ + ['apl', ['apl']], + ['bash', ['bash', 'sh']], + ['brainfuck', ['brainfuck', 'bf']], + ['c', ['c']], + ['clojure', ['clojure', 'clj']], + ['cpp', ['cpp']], + ['csharp', ['csharp', 'cs']], + ['elixir', ['elixir']], + ['fsharp', ['fsharp', 'fs']], + ['go', ['golang', 'go']], + ['haskell', ['haskell', 'hs']], + ['java', ['java']], + ['javascript', ['javascript', 'js']], + ['julia', ['julia']], + ['lua', ['lua']], + ['ocaml', ['ocaml', 'ml']], + ['pascal', ['pascal', 'pas', 'freepascal']], + ['perl', ['perl', 'pl']], + ['php', ['php']], + ['prolog', ['prolog']], + ['python', ['python', 'py']], + ['racket', ['lisp']], + ['ruby', ['ruby', 'rb']], + ['rust', ['rust', 'rs']] +]; + +Myriad.Languages = new Map(entries); +Myriad.Aliases = new Map(); +for (const [l, xs] of entries) { + for (const x of xs) { + Myriad.Aliases.set(x, l); + } +} + +module.exports = Myriad; diff --git a/src/struct/Queue.js b/src/struct/Queue.js deleted file mode 100644 index 511f479..0000000 --- a/src/struct/Queue.js +++ /dev/null @@ -1,38 +0,0 @@ -class Queue { - constructor(limit) { - this.limit = limit; - this.tasks = []; - this.ongoing = 0; - } - - get length() { - return this.tasks.length; - } - - enqueue(task) { - return new Promise((resolve, reject) => { - this.tasks.push({ task, resolve, reject }); - if (this.ongoing < this.limit) { - this.process(); - } - }); - } - - async process() { - this.ongoing++; - const { task, resolve, reject } = this.tasks.shift(); - try { - const x = await task(); - resolve(x); - } catch (e) { - reject(e); - } - - this.ongoing--; - while (this.ongoing < this.limit && this.tasks.length > 0) { - this.process(); - } - } -} - -module.exports = Queue;