Skip to content

Commit a9bbba9

Browse files
ian-h-chamberlainJRaspass
authored andcommitted
Add support for Rust lang (#133)
* Make bash scripts a little more portable build-assets: `stat` may have different arguments on different platforms, e.g. Linux usually accepts `-c %Y` to get mtime, but macOS and BSD may not. Instead, use the builtin test from `bash` to see whether `routes/assets.go` is older than the newest file in the assets dir. build-langs: This script uses associate arrays, which are only in bash >= 4. Add a simple check at the beginning to ensure the minimum bash version is being used. Makefile: Some versions of `find` do not accept predicates without a file path. This simply adds "." as the argument first * Add rust Dockerfile + add to build-langs Includes simple wrapper script that is staged to `/usr/bin/rust` which can compile from stdin or a file and immediately executes the program. Also add Rust to the build, and do a little deduplication of the list of languages being built. Strip out "rustc " prefix to get Rust version. * Add Rust to routes + views. Add to SQL schema, versions, and types, and update comment in `runCode`. * Add assets for Rust CodeMirror Rust mode and "simple" mode taken from upstream: https://github.com/codemirror/CodeMirror Rust logo taken from official Rust artwork repo: https://github.com/rust-lang/rust-artwork * Update run-lang to bind-mount /dev `rustc` requires access to `/dev/urandom` to run, but it was not available in the rootfs. This change to run-lang.asm bind-mounts "rootfs/dev" to existing "/dev" so `rustc` runs happily. Also use certain env variables that `rustc` expects in the bash wrapper, in particular `RUSTUP_HOME` is needed to find the installed Rust toolchain, and `PATH` is required to find `cc` which is used by `rustc` for linking. `RUST_BACKTRACE` is used for debugging `rustc` invocations as well as debugging the user's program, but can be removed if that seems like "cheating". * Use devtmpfs instead of bind mounting /dev The parent container has the "devtmpfs" filesystem type, which allows us to bind /dev in the same manner we bind /tmp, except it creates the expected device files (/dev/null, /dev/urandom, etc.). This also means that programs which want to use these files can (e.g. I was able to write a Python program that reads from /dev/urandom). * Only mount /dev/urandom instead of full devtmpfs Instead of creating a full devtmpfs (which includes /dev/random, /dev/null, etc.) now only /dev/urandom is mounted. This appears to be enough for `rustc` to work and probably exposes less to the user's code. * Merge remote-tracking branch 'upstream/master' into rust-lang * Alpha order Raku > Python * Update run-lang to mount /dev/random We need to mount both /dev/urandom and /dev/random, since in some cases the getrandom() syscall may not be available. This still avoids the use of devtmpfs but works in cases where the kernel does not have sufficient entropy for `getrandom()`. * Remove duplicate Raku after merge conflict * Fix copy-paste error in comment and merge problem
1 parent a486651 commit a9bbba9

File tree

15 files changed

+410
-22
lines changed

15 files changed

+410
-22
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
GOFILES := $(shell find -name '*.go' ! -path './.go*')
1+
GOFILES := $(shell find . -name '*.go' ! -path './.go*')
22
SHELL := /bin/bash
33

44
bump:

assets/hole.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/* include vendor/codemirror.js */
2+
/* include vendor/codemirror-simple.js */
3+
24
/* include vendor/codemirror-bash.js */
35
/* include vendor/codemirror-clike.js */
46
/* include vendor/codemirror-haskell.js */
@@ -13,6 +15,7 @@
1315
/* include vendor/codemirror-python.js */
1416
/* include vendor/codemirror-raku.js */
1517
/* include vendor/codemirror-ruby.js */
18+
/* include vendor/codemirror-rust.js */
1619
/* include vendor/codemirror-xml.js */
1720

1821
const chars = document.querySelector('#chars');

assets/includes/rust.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// CodeMirror, copyright (c) by Marijn Haverbeke and others
2+
// Distributed under an MIT license: https://codemirror.net/LICENSE
3+
4+
(function(mod) {
5+
if (typeof exports == "object" && typeof module == "object") // CommonJS
6+
mod(require("../../lib/codemirror"), require("../../addon/mode/simple"));
7+
else if (typeof define == "function" && define.amd) // AMD
8+
define(["../../lib/codemirror", "../../addon/mode/simple"], mod);
9+
else // Plain browser env
10+
mod(CodeMirror);
11+
})(function(CodeMirror) {
12+
"use strict";
13+
14+
CodeMirror.defineSimpleMode("rust",{
15+
start: [
16+
// string and byte string
17+
{regex: /b?"/, token: "string", next: "string"},
18+
// raw string and raw byte string
19+
{regex: /b?r"/, token: "string", next: "string_raw"},
20+
{regex: /b?r#+"/, token: "string", next: "string_raw_hash"},
21+
// character
22+
{regex: /'(?:[^'\\]|\\(?:[nrt0'"]|x[\da-fA-F]{2}|u\{[\da-fA-F]{6}\}))'/, token: "string-2"},
23+
// byte
24+
{regex: /b'(?:[^']|\\(?:['\\nrt0]|x[\da-fA-F]{2}))'/, token: "string-2"},
25+
26+
{regex: /(?:(?:[0-9][0-9_]*)(?:(?:[Ee][+-]?[0-9_]+)|\.[0-9_]+(?:[Ee][+-]?[0-9_]+)?)(?:f32|f64)?)|(?:0(?:b[01_]+|(?:o[0-7_]+)|(?:x[0-9a-fA-F_]+))|(?:[0-9][0-9_]*))(?:u8|u16|u32|u64|i8|i16|i32|i64|isize|usize)?/,
27+
token: "number"},
28+
{regex: /(let(?:\s+mut)?|fn|enum|mod|struct|type)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)/, token: ["keyword", null, "def"]},
29+
{regex: /(?:abstract|alignof|as|box|break|continue|const|crate|do|else|enum|extern|fn|for|final|if|impl|in|loop|macro|match|mod|move|offsetof|override|priv|proc|pub|pure|ref|return|self|sizeof|static|struct|super|trait|type|typeof|unsafe|unsized|use|virtual|where|while|yield)\b/, token: "keyword"},
30+
{regex: /\b(?:Self|isize|usize|char|bool|u8|u16|u32|u64|f16|f32|f64|i8|i16|i32|i64|str|Option)\b/, token: "atom"},
31+
{regex: /\b(?:true|false|Some|None|Ok|Err)\b/, token: "builtin"},
32+
{regex: /\b(fn)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)/,
33+
token: ["keyword", null ,"def"]},
34+
{regex: /#!?\[.*\]/, token: "meta"},
35+
{regex: /\/\/.*/, token: "comment"},
36+
{regex: /\/\*/, token: "comment", next: "comment"},
37+
{regex: /[-+\/*=<>!]+/, token: "operator"},
38+
{regex: /[a-zA-Z_]\w*!/,token: "variable-3"},
39+
{regex: /[a-zA-Z_]\w*/, token: "variable"},
40+
{regex: /[\{\[\(]/, indent: true},
41+
{regex: /[\}\]\)]/, dedent: true}
42+
],
43+
string: [
44+
{regex: /"/, token: "string", next: "start"},
45+
{regex: /(?:[^\\"]|\\(?:.|$))*/, token: "string"}
46+
],
47+
string_raw: [
48+
{regex: /"/, token: "string", next: "start"},
49+
{regex: /[^"]*/, token: "string"}
50+
],
51+
string_raw_hash: [
52+
{regex: /"#+/, token: "string", next: "start"},
53+
{regex: /(?:[^"]|"(?!#))*/, token: "string"}
54+
],
55+
comment: [
56+
{regex: /.*?\*\//, token: "comment", next: "start"},
57+
{regex: /.*/, token: "comment"}
58+
],
59+
meta: {
60+
dontIndentStates: ["comment"],
61+
electricInput: /^\s*\}$/,
62+
blockCommentStart: "/*",
63+
blockCommentEnd: "*/",
64+
lineComment: "//",
65+
fold: "brace"
66+
}
67+
});
68+
69+
70+
CodeMirror.defineMIME("text/x-rustsrc", "rust");
71+
CodeMirror.defineMIME("text/rust", "rust");
72+
});
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// CodeMirror, copyright (c) by Marijn Haverbeke and others
2+
// Distributed under an MIT license: https://codemirror.net/LICENSE
3+
4+
(function(mod) {
5+
if (typeof exports == "object" && typeof module == "object") // CommonJS
6+
mod(require("../../lib/codemirror"));
7+
else if (typeof define == "function" && define.amd) // AMD
8+
define(["../../lib/codemirror"], mod);
9+
else // Plain browser env
10+
mod(CodeMirror);
11+
})(function(CodeMirror) {
12+
"use strict";
13+
14+
CodeMirror.defineSimpleMode = function(name, states) {
15+
CodeMirror.defineMode(name, function(config) {
16+
return CodeMirror.simpleMode(config, states);
17+
});
18+
};
19+
20+
CodeMirror.simpleMode = function(config, states) {
21+
ensureState(states, "start");
22+
var states_ = {}, meta = states.meta || {}, hasIndentation = false;
23+
for (var state in states) if (state != meta && states.hasOwnProperty(state)) {
24+
var list = states_[state] = [], orig = states[state];
25+
for (var i = 0; i < orig.length; i++) {
26+
var data = orig[i];
27+
list.push(new Rule(data, states));
28+
if (data.indent || data.dedent) hasIndentation = true;
29+
}
30+
}
31+
var mode = {
32+
startState: function() {
33+
return {state: "start", pending: null,
34+
local: null, localState: null,
35+
indent: hasIndentation ? [] : null};
36+
},
37+
copyState: function(state) {
38+
var s = {state: state.state, pending: state.pending,
39+
local: state.local, localState: null,
40+
indent: state.indent && state.indent.slice(0)};
41+
if (state.localState)
42+
s.localState = CodeMirror.copyState(state.local.mode, state.localState);
43+
if (state.stack)
44+
s.stack = state.stack.slice(0);
45+
for (var pers = state.persistentStates; pers; pers = pers.next)
46+
s.persistentStates = {mode: pers.mode,
47+
spec: pers.spec,
48+
state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state),
49+
next: s.persistentStates};
50+
return s;
51+
},
52+
token: tokenFunction(states_, config),
53+
innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; },
54+
indent: indentFunction(states_, meta)
55+
};
56+
if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop))
57+
mode[prop] = meta[prop];
58+
return mode;
59+
};
60+
61+
function ensureState(states, name) {
62+
if (!states.hasOwnProperty(name))
63+
throw new Error("Undefined state " + name + " in simple mode");
64+
}
65+
66+
function toRegex(val, caret) {
67+
if (!val) return /(?:)/;
68+
var flags = "";
69+
if (val instanceof RegExp) {
70+
if (val.ignoreCase) flags = "i";
71+
val = val.source;
72+
} else {
73+
val = String(val);
74+
}
75+
return new RegExp((caret === false ? "" : "^") + "(?:" + val + ")", flags);
76+
}
77+
78+
function asToken(val) {
79+
if (!val) return null;
80+
if (val.apply) return val
81+
if (typeof val == "string") return val.replace(/\./g, " ");
82+
var result = [];
83+
for (var i = 0; i < val.length; i++)
84+
result.push(val[i] && val[i].replace(/\./g, " "));
85+
return result;
86+
}
87+
88+
function Rule(data, states) {
89+
if (data.next || data.push) ensureState(states, data.next || data.push);
90+
this.regex = toRegex(data.regex);
91+
this.token = asToken(data.token);
92+
this.data = data;
93+
}
94+
95+
function tokenFunction(states, config) {
96+
return function(stream, state) {
97+
if (state.pending) {
98+
var pend = state.pending.shift();
99+
if (state.pending.length == 0) state.pending = null;
100+
stream.pos += pend.text.length;
101+
return pend.token;
102+
}
103+
104+
if (state.local) {
105+
if (state.local.end && stream.match(state.local.end)) {
106+
var tok = state.local.endToken || null;
107+
state.local = state.localState = null;
108+
return tok;
109+
} else {
110+
var tok = state.local.mode.token(stream, state.localState), m;
111+
if (state.local.endScan && (m = state.local.endScan.exec(stream.current())))
112+
stream.pos = stream.start + m.index;
113+
return tok;
114+
}
115+
}
116+
117+
var curState = states[state.state];
118+
for (var i = 0; i < curState.length; i++) {
119+
var rule = curState[i];
120+
var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex);
121+
if (matches) {
122+
if (rule.data.next) {
123+
state.state = rule.data.next;
124+
} else if (rule.data.push) {
125+
(state.stack || (state.stack = [])).push(state.state);
126+
state.state = rule.data.push;
127+
} else if (rule.data.pop && state.stack && state.stack.length) {
128+
state.state = state.stack.pop();
129+
}
130+
131+
if (rule.data.mode)
132+
enterLocalMode(config, state, rule.data.mode, rule.token);
133+
if (rule.data.indent)
134+
state.indent.push(stream.indentation() + config.indentUnit);
135+
if (rule.data.dedent)
136+
state.indent.pop();
137+
var token = rule.token
138+
if (token && token.apply) token = token(matches)
139+
if (matches.length > 2 && rule.token && typeof rule.token != "string") {
140+
state.pending = [];
141+
for (var j = 2; j < matches.length; j++)
142+
if (matches[j])
143+
state.pending.push({text: matches[j], token: rule.token[j - 1]});
144+
stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0));
145+
return token[0];
146+
} else if (token && token.join) {
147+
return token[0];
148+
} else {
149+
return token;
150+
}
151+
}
152+
}
153+
stream.next();
154+
return null;
155+
};
156+
}
157+
158+
function cmp(a, b) {
159+
if (a === b) return true;
160+
if (!a || typeof a != "object" || !b || typeof b != "object") return false;
161+
var props = 0;
162+
for (var prop in a) if (a.hasOwnProperty(prop)) {
163+
if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false;
164+
props++;
165+
}
166+
for (var prop in b) if (b.hasOwnProperty(prop)) props--;
167+
return props == 0;
168+
}
169+
170+
function enterLocalMode(config, state, spec, token) {
171+
var pers;
172+
if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next)
173+
if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p;
174+
var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec);
175+
var lState = pers ? pers.state : CodeMirror.startState(mode);
176+
if (spec.persistent && !pers)
177+
state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates};
178+
179+
state.localState = lState;
180+
state.local = {mode: mode,
181+
end: spec.end && toRegex(spec.end),
182+
endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false),
183+
endToken: token && token.join ? token[token.length - 1] : token};
184+
}
185+
186+
function indexOf(val, arr) {
187+
for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true;
188+
}
189+
190+
function indentFunction(states, meta) {
191+
return function(state, textAfter, line) {
192+
if (state.local && state.local.mode.indent)
193+
return state.local.mode.indent(state.localState, textAfter, line);
194+
if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1)
195+
return CodeMirror.Pass;
196+
197+
var pos = state.indent.length - 1, rules = states[state.state];
198+
scan: for (;;) {
199+
for (var i = 0; i < rules.length; i++) {
200+
var rule = rules[i];
201+
if (rule.data.dedent && rule.data.dedentIfLineStart !== false) {
202+
var m = rule.regex.exec(textAfter);
203+
if (m && m[0]) {
204+
pos--;
205+
if (rule.next || rule.push) rules = states[rule.next || rule.push];
206+
textAfter = textAfter.slice(m[0].length);
207+
continue scan;
208+
}
209+
}
210+
}
211+
break;
212+
}
213+
return pos < 0 ? 0 : state.indent[pos];
214+
};
215+
}
216+
});

build-assets

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#!/bin/bash -e
22

33
# Only re-run if assets.go is stale.
4-
if (( $(stat -c %Y `ls -t assets/{,*/}* | head -1`) > `stat -c %Y routes/assets.go || echo 0` )); then
4+
newest_file=$(ls -t assets/{,*/}* | head -1)
5+
if [[ routes/assets.go -ot $newest_file ]]; then
56
docker build --pull -t code-golf-assets assets
67
docker run --rm -u $UID:$GROUPS -v $PWD:/work code-golf-assets
78
fi

0 commit comments

Comments
 (0)