Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bytes"
"context"
"errors"
"flag"
Expand Down Expand Up @@ -80,7 +81,7 @@ func compileAndRunCode(w http.ResponseWriter, tempDir string) (string, bool) {
return "", false
}

func postHandler(w http.ResponseWriter, r *http.Request) {
func compileHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
Expand All @@ -98,21 +99,50 @@ func postHandler(w http.ResponseWriter, r *http.Request) {

os.Chmod(tempDir, 0777)
codePath := filepath.Join(tempDir, "main.jule")
codeInput, _ := io.ReadAll(r.Body)
os.WriteFile(codePath, codeInput, 0644)
inputCode, _ := io.ReadAll(r.Body)
os.WriteFile(codePath, inputCode, 0644)

if outputMessage, ok := compileAndRunCode(w, tempDir); ok {
fmt.Fprint(w, outputMessage)
}
}

func formatHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

inputCode, _ := io.ReadAll(r.Body)

// When an error occurs, julefmt writes to stderr but still returns 0 as an exit code.
// Therefore you have to check directly stderr and stdout content.
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd := exec.Command("julefmt")
cmd.Stdin = strings.NewReader(string(inputCode))
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()

if err != nil {
errorMessage := fmt.Sprintf("Exit error: %v\n", err)
http.Error(w, errorMessage, 500)
} else if stderr.Len() != 0 {
http.Error(w, stderr.String(), 500)
} else if stdout.Len() != 0 {
fmt.Fprint(w, stdout.String())
}
}

func main() {
port := flag.Int("port", 8080, "server port")
flag.Parse()
fs := http.FileServer(http.Dir("./public"))
http.Handle("/playground/", http.StripPrefix("/playground/", fs))

http.HandleFunc("/playground/compile", postHandler)
http.HandleFunc("/playground/compile", compileHandler)
http.HandleFunc("/playground/format", formatHandler)
addr := fmt.Sprintf(":%d", *port)
fmt.Println("http://0.0.0.0" + addr + "/playground/")
log.Fatal(http.ListenAndServe(addr, nil))
Expand Down
142 changes: 92 additions & 50 deletions public/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -20509,8 +20509,11 @@ var basicSetup = /* @__PURE__ */ (() => [

// public/playground.js
var isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
var runButton = document.getElementById("run-button");
var formatButton = document.getElementById("format-button");
if (isMobile) {
document.getElementById("run-button").innerText = "Run";
runButton.innerText = "Run";
formatButton.innerText = "Format";
}
var types2 = [
"bool",
Expand Down Expand Up @@ -20629,22 +20632,23 @@ var jule = StreamLanguage.define({
}
});
var helloWorldCode = `fn main() {
println("Hello World!")
println("Hello World!")
}`;
var editor = new EditorView({
doc: helloWorldCode,
extensions: [
basicSetup,
jule,
syntaxHighlighting(style),
keymap.of([indentWithTab])
keymap.of([indentWithTab]),
indentUnit.of("\t")
],
parent: document.getElementById("editor")
});
var isCompiling = false;
var runButton = document.getElementById("run-button");
var isFormatting = false;
runButton.onclick = () => {
if (isCompiling) {
if (isCompiling || isFormatting) {
return;
}
isCompiling = true;
Expand Down Expand Up @@ -20673,74 +20677,112 @@ document.addEventListener("keydown", (e) => {
runButton.click();
}
}, { capture: true });
formatButton.onclick = () => {
if (isFormatting || isCompiling) {
return;
}
isFormatting = true;
const outputElement = document.getElementById("output");
const inputCode = editor.state.doc.toString();
fetch("/playground/format", {
method: "POST",
body: inputCode,
headers: { "Content-Type": "text/plain" }
}).then(async (res) => {
if (res.status >= 500) {
const message = await res.text();
throw message;
}
return res.text();
}).then((formattedCode) => {
editor.dispatch({
changes: {
from: 0,
to: editor.state.doc.length,
insert: formattedCode
}
});
outputElement.textContent = "Code formatted successfully.";
isFormatting = false;
}).catch((err) => {
outputElement.textContent = err;
isFormatting = false;
});
isFormatting = false;
};
document.addEventListener("keydown", (e) => {
if (e.shiftKey && e.key === "Enter") {
e.preventDefault();
formatButton.click();
}
}, { capture: true });
var examples = document.getElementById("examples");
examples.onchange = (e) => {
const value = e.target.value;
let newCode = helloWorldCode;
switch (value) {
case "fizzbuzz":
newCode = `fn main() {
mut i := 1
for i <= 16; i++ {
if i % 15 == 0 {
println("FizzBuzz")
} else if i % 3 == 0 {
println("Fizz")
} else if i % 5 == 0 {
println("Buzz")
}
}
mut i := 1
for i <= 16; i++ {
if i%15 == 0 {
println("FizzBuzz")
} else if i%3 == 0 {
println("Fizz")
} else if i%5 == 0 {
println("Buzz")
}
}
}`;
break;
case "randomness":
newCode = `use "std/fmt"
use "std/math/rand"
use "std/time"

fn main() {
// Constants are compile-time known values
const min = 1
const max = 10
// Constants are compile-time known values
const min = 1
const max = 10

random_number := rand::IntN(max - min) + min
random_number := rand::IntN(max-min) + min

// print[ln] doesn't accept multiple arguments, so you have to use fmt::Print
fmt::Print("Here is a number between ", min, " and ", max, ": ")
println(random_number)
// print[ln] doesn't accept multiple arguments, so you have to use fmt::Print
fmt::Print("Here is a number between ", min, " and ", max, ": ")
println(random_number)
}`;
break;
case "comptime-matching":
newCode = `fn printKind[T](value: T) {
const match type T {
| *int:
println("int pointer")
| &int:
println("int reference")
| u32:
println("u32")
| i32:
println("i32")
| u8:
println("u8")
| cmplx128:
println("cmplx128")
| cmplx64:
println("cmplx64")
| []int:
println("slice of ints")
| [5]int:
println("array of 5 ints")
|:
panic("unexpected type")
}
const match type T {
| *int:
println("int pointer")
| &int:
println("int reference")
| u32:
println("u32")
| i32:
println("i32")
| u8:
println("u8")
| cmplx128:
println("cmplx128")
| cmplx64:
println("cmplx64")
| []int:
println("slice of ints")
| [5]int:
println("array of 5 ints")
|:
panic("unexpected type")
}
}

fn main() {
let x: [5]int = [1, 2, 3, 4, 5]
printKind(x)
printKind(3+4i)
slice := [2, 3, 4]
printKind(slice)
let x: [5]int = [1, 2, 3, 4, 5]
printKind(x)
printKind(3 + 4i)
slice := [2, 3, 4]
printKind(slice)
}`;
break;
}
Expand Down
5 changes: 4 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ <h1>Jule Playground</h1>
</div>
<div id="output-wrapper">
<script type="module" src="./bundle.js"></script>
<button type="button" id="run-button">Run (Ctrl-Enter)</button>
<div id="buttons">
<button type="button" id="run-button">Run (Ctrl-Enter)</button>
<button type="button" id="format-button">Format (Shift-Enter)</button>
</div>
<pre id="output">
clang version 21.1.2
jule0.2.0</pre>
Expand Down
Loading
Loading