--{{0}}--
This project allows you to run a code-running server, based on Python, that can compile and execute code and communicate via websockets. Thus, if you want to develop some interactive online courses, this is probably a good way to start. This README is also a self-contained LiaScript template, that defines some basic macros, which can be used to make your Markdown code-snippets executable.
Try it on LiaScript:
https://liascript.github.io/course/?https://github.com/liascript/CodeRunner
See the project on Github:
https://github.com/liascript/CodeRunner
--{{1}}--
There are three ways to use this template. The easiest way is to use the
import
statement and the URL of the raw text-file of the master branch or any
other branch or version. But you can also copy the required functionality
directly into the header of your Markdown document, see therefor the
last slide. And of course, you could also clone this project
and change it, as you wish.
{{1}}
-
Load the macros via
import: https://raw.githubusercontent.com/LiaScript/CodeRunner/master/README.md
-
Copy the definitions into your Project
-
Clone this repository on GitHub
You only have to attach the command @LIA.eval
to your code-block or project
and pass three parameters.
- The first, is a list of filenames, the number of sequential code-blocks defines the naming order.
- Then pass the command how your code should be compiled
- And as the last part, how to execute your code.
```c
#include <stdio.h>
int main (void){
printf ("Hello, world \n");
return 0;
}
```
@LIA.eval(`["main.c"]`, `gcc -Wall main.c -o a.out`, `./a.out`)
In most cases it is sufficient to have only one file.
For this purpose we also provide shortcuts, such that the complex functionality above can be simplified with only the macro @LIA.c
.
These shortcuts always assume one file only.
#include <stdio.h>
int main (void){
int i = 0;
int max = 0;
printf("How many hellos: ");
scanf("%d",&max);
for(i=0; i<max; i++)
printf ("Hello, world %d!\n", i);
return 0;
}
@LIA.eval(["main.c"]
, gcc -Wall main.c -o a.out
, ./a.out
)
(ns clojure.examples.hello
(:gen-class))
(defn hello-world []
(println "Hello World"))
(hello-world)
@LIA.clojure
#include <iostream>
using namespace std;
int main (){
int i = 0;
int max = 0;
cout << "How many hellos: ";
cin >> max;
for(i=0; i<max; i++)
cout << "Hello, world " << i << endl;
return 0;
}
@LIA.eval(["main.cpp"]
, g++ main.cpp -o a.out
, ./a.out
)
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@LIA.eval(["Program.cs", "project.csproj"]
, dotnet build -nologo
, dotnet run
)
The macro @LIA.dotnet
already includes a default project file.
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
@LIA.dotnet
// See https://aka.ms/new-console-template for more information
printfn "Hello from F#"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
</Project>
@LIA.eval(["Program.fs", "project.fsproj"]
, dotnet build -nologo
, dotnet run
)
// See https://aka.ms/new-console-template for more information
printfn "Hello from F#"
@LIA.dotnetFsharp
main = putStrLn "hello world"
@LIA.eval(["main.hs"]
, ghc main.hs -o main
, ./main
)
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
@LIA.eval(["main.go"]
, go build main.go
, ./main
)
import java.io.*;
class Demo {
public static void main(String args[])
throws IOException
{
// create a BufferedReader using System.in
BufferedReader obj = new BufferedReader(new InputStreamReader(System.in));
String str;
System.out.println("Enter lines of text.");
System.out.println("Enter 'stop' to quit.");
do {
str = obj.readLine();
System.err.println(str);
} while(!str.equals("stop"));
}
}
@LIA.eval(["Demo.java"]
, javac Demo.java
, java Demo
)
The short-cut for java requires a special parameter, which is the name of the class, such that this can be substituted within filenames and commands.
import java.io.*;
class Demo {
public static void main(String args[])
throws IOException
{
// create a BufferedReader using System.in
BufferedReader obj = new BufferedReader(new InputStreamReader(System.in));
String str;
System.out.println("Enter lines of text.");
System.out.println("Enter 'stop' to quit.");
do {
str = obj.readLine();
System.err.println(str);
} while(!str.equals("stop"));
}
}
@LIA.java(Demo)
# function to calculate the volume of a sphere
function sphere_vol(r)
# julia allows Unicode names (in UTF-8 encoding)
# so either "pi" or the symbol π can be used
return 4/3*pi*r^3
end
# functions can also be defined more succinctly
quadratic(a, sqr_term, b) = (-b + sqr_term) / 2a
# calculates x for 0 = a*x^2+b*x+c, arguments types can be defined in function definitions
function quadratic2(a::Float64, b::Float64, c::Float64)
# unlike other languages 2a is equivalent to 2*a
# a^2 is used instead of a**2 or pow(a,2)
sqr_term = sqrt(b^2-4a*c)
r1 = quadratic(a, sqr_term, b)
r2 = quadratic(a, -sqr_term, b)
# multiple values can be returned from a function using tuples
# if the return keyword is omitted, the last term is returned
r1, r2
end
vol = sphere_vol(3)
# @printf allows number formatting but does not automatically append the \n to statements, see below
using Printf
@printf "volume = %0.3f\n" vol
#> volume = 113.097
quad1, quad2 = quadratic2(2.0, -2.0, -12.0)
println("result 1: ", quad1)
#> result 1: 3.0
println("result 2: ", quad2)
#> result 2: -2.0
@LIA.julia
/*
* C# Program to Check whether the Entered Number is Even or Odd
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace check1
{
class Program
{
static void Main(string[] args)
{
int i;
Console.Write("Enter a Number : ");
i = int.Parse(Console.ReadLine());
if (i % 2 == 0)
{
Console.Write("Entered Number is an Even Number");
}
else
{
Console.Write("Entered Number is an Odd Number");
}
}
}
}
@LIA.eval(["main.cs"]
, mcs main.cs
, mono main.exe
)
; ----------------------------------------------------------------------------------------
; Writes "Hello, World" to the console using only system calls. Runs on 64-bit Linux only.
; To assemble and run:
;
; nasm -felf64 main.asm && ld main.o && ./a.out
; ----------------------------------------------------------------------------------------
global _start
section .text
_start: mov rax, 1 ; system call for write
mov rdi, 1 ; file handle 1 is stdout
mov rsi, message ; address of string to output
mov rdx, 13 ; number of bytes
syscall ; invoke operating system to do the write
mov rax, 60 ; system call for exit
xor rdi, rdi ; exit code 0
syscall ; invoke operating system to exit
section .data
message: db "Hello, World", 10 ; note the newline at the end
@LIA.eval(["main.asm"]
, nasm -felf64 main.asm && ld main.o
, ./a.out
)
echo "Hello World"
@LIA.eval(["main.nim"]
, nim c main.nim
, ./main
)
for i in range(10):
print "Hallo Welt", i
@LIA.eval(["main.py"]
, python2.7 -m compileall .
, python2.7 main.pyc
)
for i in range(10):
print("Hallo Welt", i)
@LIA.eval(["main.py"]
, none
, python3 main.py
, *.py
)
A,B,C
0,0.1,3
1,0.3,5
2,0.4,2
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv('data.csv', header = 0)
df.plot.scatter(x='A', y='B')
plt.savefig('temp.png')
@LIA.eval(["data.csv", "main.py"]
, none
, python3 main.py
, *
)
print("Hello World")
@LIA.eval(["main.R"]
, none
, Rscript main.R
)
library(ggplot2)
# Use stdout as per normal...
print("Hello, world!")
# Use plots...
png(file="out1.png")
plot(cars)
# Even ggplot!
png(file="out2.png")
qplot(wt, mpg, data = mtcars, colour = factor(cyl))
@LIA.r
fn main() {
println!("Hello World!");
}
@LIA.rust
const std = @import("std");
pub fn main() void {
std.io.getStdOut().writeAll(
"Hello World!",
) catch unreachable;
}
@LIA.zig
println("Hello World")
@LIA.v
print "Enter your name: ";
$name=<STDIN>;
print "Hello, ${name} ... you will soon be a Perl addict!\n";
@LIA.perl
class HelloWorld
def initialize(name)
@name = name.capitalize
end
def sayHi
puts "Hello #{@name}!"
end
end
hello = HelloWorld.new("World")
hello.sayHi
@LIA.ruby
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
begin
Put_Line ("Hello WORLD!");
end Main;
@LIA.eval(["main.ada"]
, gnatmake main.ada
, ./main
)
This does basically the same as @LIA.eval
, but it will add additional
Debug-information about the CodeRunner status to the console.
#include <stdio.h>
int main (void){
int i = 0;
int max = 0;
printf("How many hellos: ");
scanf("%d",&max);
for(i=0; i<max; i++)
printf ("Hello, world %d!\n", i);
return 0;
}
@LIA.evalWithDebug(["main.c"]
, gcc -Wall main.c -o a.out
, ./a.out
)
If you deploy this to heroku, as we do, keep in mind, that the free service will be shut down, if no one uses it for 30 minutes, it takes round about 30 sec. to resurrect.
-
Install the Heroku-CLI
-
Create a new Heroku project
- Login to Heroku:
heroku login
(Don't use sudo or it will not work!) - Create the project:
heroku create [app_name]
- Login to Heroku:
-
Login to Heroku container:
heroku container:login
(It's important that you have docker installed before executing this command and make sure that your user is added to the docker group.) -
Build the docker container and upload it to heroku:
docker build . -t web
heroku container:push web -a app_name
-
Release the docker container:
heroku container:release web -a app_name
Your project url is now app_name.herokuapp.com
. (Or the auto-generated one
when you haven't supplied an app name.)
If you deploy your own server, you have to change the websocket-url in the main
header (main HTML comment of your Markdown document) from
wss://liarunner.herokuapp.com/socket
to wss://*******.herokuapp.com/socket
...
what ever the name of your app is ...
--{{0}}--
If you want to minimize loading effort in your LiaScript project, you can also copy this code and paste it into your main comment header, see the code in the raw file of this document.
{{1}} https://raw.githubusercontent.com/liaScript/CodeRunner/master/README.md
onload
window.CodeRunner = {
ws: undefined,
handler: {},
init(url) {
this.ws = new WebSocket(url);
const self = this
this.ws.onopen = function () {
self.log("connections established");
setInterval(function() {
self.ws.send("ping")
}, 15000);
}
this.ws.onmessage = function (e) {
// e.data contains received string.
let data
try {
data = JSON.parse(e.data)
} catch (e) {
self.warn("received message could not be handled =>", e.data)
}
if (data) {
self.handler[data.uid](data)
}
}
this.ws.onclose = function () {
self.warn("connection closed")
}
this.ws.onerror = function (e) {
self.warn("an error has occurred => ", e)
}
},
log(...args) {
console.log("CodeRunner:", ...args)
},
warn(...args) {
console.warn("CodeRunner:", ...args)
},
handle(uid, callback) {
this.handler[uid] = callback
},
send(uid, message) {
message.uid = uid
this.ws.send(JSON.stringify(message))
}
}
window.CodeRunner.init("wss://coderunner.informatik.tu-freiberg.de/")
//window.CodeRunner.init("ws://127.0.0.1:8000/")
@end
@LIA.c: @LIA.eval(`["main.c"]`, `gcc -Wall main.c -o a.out`, `./a.out`)
@LIA.clojure: @LIA.eval(`["main.clj"]`, `none`, `clojure -M main.clj`)
@LIA.cpp: @LIA.eval(`["main.cpp"]`, `g++ main.cpp -o a.out`, `./a.out`)
@LIA.go: @LIA.eval(`["main.go"]`, `go build main.go`, `./main`)
@LIA.haskell: @LIA.eval(`["main.hs"]`, `ghc main.hs -o main`, `./main`)
@LIA.java: @LIA.eval(`["@0.java"]`, `javac @0.java`, `java @0`)
@LIA.julia: @LIA.eval(`["main.jl"]`, `none`, `julia main.jl`)
@LIA.mono: @LIA.eval(`["main.cs"]`, `mcs main.cs`, `mono main.exe`)
@LIA.nasm: @LIA.eval(`["main.asm"]`, `nasm -felf64 main.asm && ld main.o`, `./a.out`)
@LIA.python: @LIA.python3
@LIA.python2: @LIA.eval(`["main.py"]`, `python2.7 -m compileall .`, `python2.7 main.pyc`)
@LIA.python3: @LIA.eval(`["main.py"]`, `none`, `python3 main.py`)
@LIA.python3: @LIA.eval(`["main.pl"]`, `none`, `perl main.pl`)
@LIA.r: @LIA.eval(`["main.R"]`, `none`, `Rscript main.R`)
@LIA.rust: @LIA.eval(`["main.rs"]`, `rustc main.rs`, `./main`)
@LIA.v: @LIA.eval(`["main.v"]`, `v main.v`, `./main`)
@LIA.zig: @LIA.eval(`["main.zig"]`, `zig build-exe ./main.zig -O ReleaseSmall`, `./main`)
@LIA.dotnet
```xml -project.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
```
@LIA.eval(`["Program.cs","project.csproj"]`, `dotnet build -nologo`, `dotnet run`)
@end
@LIA.dotnetFsharp
```xml -project.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
</Project>
```
@LIA.eval(`["Program.fs", "project.fsproj"]`, `dotnet build -nologo`, `dotnet run`)
@end
@LIA.eval: @LIA.eval_(false,`@0`,@1,@2)
@LIA.evalWithDebug: @LIA.eval_(true,`@0`,@1,@2)
@LIA.eval_
<script>
function random(len=16) {
let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let str = '';
for (let i = 0; i < len; i++) {
str += chars.charAt(Math.floor(Math.random() * chars.length));
}
return str;
}
const uid = random()
var order = @1
var files = []
if (order[0])
files.push([order[0], `@'input(0)`])
if (order[1])
files.push([order[1], `@'input(1)`])
if (order[2])
files.push([order[2], `@'input(2)`])
if (order[3])
files.push([order[3], `@'input(3)`])
if (order[4])
files.push([order[4], `@'input(4)`])
if (order[5])
files.push([order[5], `@'input(5)`])
if (order[6])
files.push([order[6], `@'input(6)`])
if (order[7])
files.push([order[7], `@'input(7)`])
if (order[8])
files.push([order[8], `@'input(8)`])
if (order[9])
files.push([order[9], `@'input(9)`])
send.handle("input", (e) => {
CodeRunner.send(uid, {stdin: e})
})
send.handle("stop", (e) => {
CodeRunner.send(uid, {stop: true})
});
CodeRunner.handle(uid, function (msg) {
switch (msg.service) {
case 'data': {
if (msg.ok) {
CodeRunner.send(uid, {compile: @2})
}
else {
send.lia("LIA: stop")
}
break;
}
case 'compile': {
if (msg.ok) {
if (msg.message) {
if (msg.problems.length)
console.warn(msg.message);
else
console.log(msg.message);
}
send.lia("LIA: terminal")
CodeRunner.send(uid, {exec: @3})
if(!@0) {
console.clear()
}
} else {
send.lia(msg.message, msg.problems, false)
send.lia("LIA: stop")
}
break;
}
case 'stdout': {
if (msg.ok)
console.stream(msg.data)
else
console.error(msg.data);
break;
}
case 'stop': {
if (msg.error) {
console.error(msg.error);
}
if (msg.images) {
for(let i = 0; i < msg.images.length; i++) {
console.html("<hr/>", msg.images[i].file)
console.html("<img title='" + msg.images[i].file + "' src='" + msg.images[i].data + "' onclick='window.LIA.img.click(\"" + msg.images[i].data + "\")'>")
}
}
send.lia("LIA: stop")
break;
}
default:
console.log(msg)
break;
}
})
CodeRunner.send(
uid, { "data": files }
);
"LIA: wait"
</script>
@end
Change the Dockerfile to:
...
# EXPOSE 8000
# ENTRYPOINT python3 -m server
CMD python3 -m server --host 0.0.0.0 --port $PORT
The host has to be set to 0.0.0.0
and the port is set by heroku itself.
Afterwards repeat the following steps:
$ heroku container:login
...
Login Succeeded
$ heroku create
Creating app... done, ⬢ XXXXXX-XXXXXXX-XXXXXX
https://XXXXXX-XXXXXXX-XXXXXX.herokuapp.com/ | https://git.heroku.com/XXXXXX-XXXXXXX-XXXXXX.git
$ heroku container:push web
=== Building web (.../CodeRunner/Dockerfile)
Sending build context to Docker daemon 4.633MB
Step 1/35 : FROM ubuntu:kinetic
---> d6547859cd2f
Step 2/35 : RUN DEBIAN_FRONTEND=noninteractive apt-get update --fix-missing
---> Using cache
...
Step 35/35 : CMD python3 -m server --host 0.0.0.0 --port $PORT
---> Running in bde2634a12ba
...
Successfully built 50ec74c6e81f
Successfully tagged registry.heroku.com/XXXXXX-XXXXXXX-XXXXXX/web:latest
=== Pushing web (.../CodeRunner/Dockerfile)
Using default tag: latest
The push refers to repository [registry.heroku.com/XXXXXX-XXXXXXX-XXXXXX/web]
...
Your image has been successfully pushed. You can now release it with the 'container:release' command.
$ heroku container:release web
Releasing images web to XXXXXX-XXXXXXX-XXXXXX... done