Skip to content

Commit 59899dd

Browse files
committed
feat: implement run + puts/p support
1 parent c7ca6a9 commit 59899dd

File tree

3 files changed

+172
-30
lines changed

3 files changed

+172
-30
lines changed

src/app.js

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,49 +16,153 @@ puts greet.call(hello: 'martian')
1616

1717
const DEFAULT_PREVIEW = `# Here you will see the transpiled source code.`;
1818

19+
const CONFIG = `# Here you can define custom source rewriters.`;
20+
21+
const OUTPUT = "// Here will be the output of your program";
22+
23+
const THEMES = {
24+
light: "vs",
25+
dark: "vs-dark",
26+
};
27+
1928
export default class App {
2029
constructor(el, vm, monaco) {
2130
this.el = el;
2231
this.vm = vm;
2332
this.monaco = monaco;
33+
34+
this.onSelectEditor = this.onSelectEditor.bind(this);
2435
}
2536

2637
bootstrap() {
27-
this.currentEditor = this.initEditor(
28-
this.el.querySelector('[target="editor"]'),
38+
const theme = window.matchMedia?.("(prefers-color-scheme: dark)").matches
39+
? "dark"
40+
: "light";
41+
42+
this.monaco.editor.setTheme(THEMES[theme]);
43+
44+
this.codeEditor = this.initEditor(
45+
this.el.querySelector("#codeEditor"),
2946
DEFAULT_SOURCE
3047
);
3148

49+
this.configEditor = this.initEditor(
50+
this.el.querySelector("#configEditor"),
51+
CONFIG
52+
);
53+
3254
this.previewEditor = this.initEditor(
33-
this.el.querySelector('[target="preview"]'),
55+
this.el.querySelector("#previewEditor"),
3456
DEFAULT_PREVIEW,
3557
{
3658
readOnly: true,
3759
}
3860
);
3961

62+
this.outputEditor = this.initEditor(
63+
this.el.querySelector("#outputEditor"),
64+
OUTPUT,
65+
{
66+
readOnly: true,
67+
language: "shell",
68+
lineNumbers: "off",
69+
}
70+
);
71+
4072
this.el
4173
.querySelector('[target="transpile-btn"]')
4274
.addEventListener("click", () => {
43-
const code = this.currentEditor.getValue();
44-
const result = this.vm
45-
.eval("RubyNext::Language.transform(%q(" + code + "), using: false)")
46-
.toString();
75+
const result = this.transpile(this.codeEditor.getValue());
4776

4877
this.previewEditor.setValue(result);
78+
this.showEditor("previewEditor");
79+
});
80+
81+
this.el
82+
.querySelector('[target="run-btn"]')
83+
.addEventListener("click", () => {
84+
const newSource = this.transpile(this.codeEditor.getValue());
85+
this.previewEditor.setValue(newSource);
86+
87+
const result = this.execute(newSource);
88+
let output = window.$puts.flush();
89+
90+
if (result) output += "\n\n> " + result;
91+
92+
this.outputEditor.setValue(output);
93+
94+
this.showEditor("outputEditor");
4995
});
96+
97+
this.el.addEventListener("change", this.onSelectEditor);
98+
99+
this.setCurrentVersion();
100+
}
101+
102+
transpile(code) {
103+
const result = this.vm
104+
.eval("RubyNext.transform(%q(" + code + "))")
105+
.toString();
106+
107+
return result;
108+
}
109+
110+
execute(source) {
111+
try {
112+
return this.vm.eval(source).toString();
113+
} catch (e) {
114+
console.error(e);
115+
return e.message;
116+
}
117+
}
118+
119+
async setCurrentVersion() {
120+
const versionContainer = document.getElementById("currentVersion");
121+
if (!versionContainer) return;
122+
123+
const version = this.execute("RUBY_VERSION + '-' + RUBY_PLATFORM");
124+
125+
versionContainer.innerText = version;
50126
}
51127

52128
initEditor(target, value, opts = {}) {
53129
return this.monaco.editor.create(target, {
54130
value,
55131
language: "ruby",
56-
theme: "vs-dark",
57132
automaticLayout: true,
58133
minimap: {
59134
enabled: false,
60135
},
61136
...opts,
62137
});
63138
}
139+
140+
showEditor(editorName) {
141+
const editor = this.el.querySelector(`#${editorName}`);
142+
if (!editor) return;
143+
144+
const containerId = editor.dataset.pane;
145+
if (!containerId) return;
146+
147+
const container = this.el.querySelector(`#${containerId}`);
148+
if (!container) return;
149+
150+
// Hide previous editor
151+
container
152+
.querySelector("[data-pane]:not(.hidden)")
153+
?.classList.add("hidden");
154+
155+
// Make sure the correct radio is checked
156+
const radio = container.querySelector(`input[value="${editorName}"]`);
157+
if (radio) radio.checked = true;
158+
159+
// Show the editor
160+
editor.classList.remove("hidden");
161+
}
162+
163+
onSelectEditor(e) {
164+
if (!e.target.value) return;
165+
166+
this.showEditor(e.target.value);
167+
}
64168
}

src/index.html

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<h2>Initializing Ruby Next playground...</h2>
1616
<ul class="mt-2 text-sm text-slate-500 dark:text-slate-400">
1717
<li class="flex flex-row items-center" id="loader-editor">
18-
<i data-icon="loading"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 p-1" viewBox="0 0 24 24"><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"><animateTransform attributeName="transform" type="rotate" dur="0.75s" values="0 12 12;360 12 12" repeatCount="indefinite"/></path></svg></i>
18+
<i data-icon="loading"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 p-1" stroke="currentColor" viewBox="0 0 24 24"><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"><animateTransform attributeName="transform" type="rotate" dur="0.75s" values="0 12 12;360 12 12" repeatCount="indefinite"/></path></svg></i>
1919
<i data-icon="check" class="hidden text-green-600"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
2020
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
2121
</svg></i>
@@ -27,7 +27,7 @@ <h2>Initializing Ruby Next playground...</h2>
2727
<span class="ml-1">Code Editor</span>
2828
</li>
2929
<li class="flex flex-row items-center" id="loader-ruby">
30-
<i data-icon="loading"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 p-1" viewBox="0 0 24 24"><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"><animateTransform attributeName="transform" type="rotate" dur="0.75s" values="0 12 12;360 12 12" repeatCount="indefinite"/></path></svg></i>
30+
<i data-icon="loading"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 p-1" stroke="currentColor" viewBox="0 0 24 24"><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"><animateTransform attributeName="transform" type="rotate" dur="0.75s" values="0 12 12;360 12 12" repeatCount="indefinite"/></path></svg></i>
3131
<i data-icon="check" class="hidden text-green-600"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
3232
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
3333
</svg></i>
@@ -44,8 +44,9 @@ <h2>Initializing Ruby Next playground...</h2>
4444
<div id="app" class="hidden w-full h-screen flex flex-col">
4545
<nav class="flex-shrink flex flex-row justify-between items-middle border-b-1 border-slate-300 shadow-md">
4646
<div class="flex flex-row items-center justify-start">
47-
<img class="mx-2 h-12 w-auto" src="/assets/text-logo.png" alt="Ruby Next">
48-
<h1 class="text-2xl border-l-2 pl-2 border-slate-400">Playground</h1>
47+
<img class="mx-2 p-1 h-12 w-auto" src="/assets/logo.png" alt="Ruby Next">
48+
<h1 class="text-2xl">Playground</h1>
49+
<span class="text-sm ml-4 h-4" id="currentVersion"></span>
4950
</div>
5051
<div class="flex flex-row space-x-2 self-center justify-center items-center px-2">
5152
<button target="transpile-btn">Transpile</button>
@@ -63,45 +64,47 @@ <h1 class="text-2xl border-l-2 pl-2 border-slate-400">Playground</h1>
6364
</div>
6465
</nav>
6566
<div class="flex flex-row flex-grow relative">
66-
<div class="flex flex-col w-1/2 relative">
67-
<div class="flex flex-row flex-grow text-sm border-slate-300">
68-
<label for="editor_left_pane_config" class="border p-1 flex flex-row space-x-1 items-center has-[:checked]:bg-editor-light dark:has-[:checked]:bg-editor-dark">
69-
<input type="radio" id="editor_left_pane_config" name="editor_left_pane" value="config" class="hidden peer">
67+
<div id="editor_left_pane" class="flex flex-col w-1/2 relative">
68+
<div class="flex flex-row flex-grow text-sm bg-editor-light dark:bg-editor-dark">
69+
<label for="editor_left_pane_config" class="mb-1 p-1 flex flex-row items-center cursor-pointer border-b border-editor-light dark:border-editor-dark has-[:checked]:border-editor-dark dark:has-[:checked]:border-editor-light opacity-75 has-[:checked]:opacity-100">
70+
<input type="radio" id="editor_left_pane_config" name="editor_left_pane" value="configEditor" class="hidden peer">
7071
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
7172
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 0 1 1.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.559.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.894.149c-.424.07-.764.383-.929.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 0 1-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.398.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 0 1-.12-1.45l.527-.737c.25-.35.272-.806.108-1.204-.165-.397-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 0 1 .12-1.45l.773-.773a1.125 1.125 0 0 1 1.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894Z" />
7273
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
7374
</svg>
74-
<span>Config</span>
75+
<span class="ml-1">Config</span>
7576
</label>
76-
<label for="editor_left_pane_code" class="border p-1 flex flex-row space-x-1 items-center has-[:checked]:bg-editor-light dark:peer-checked:bg-editor-dark">
77-
<input type="radio" id="editor_left_pane_code" name="editor_left_pane" value="code" class="hidden peer" checked>
77+
<label for="editor_left_pane_code" class="mb-1 p-1 flex flex-row items-center cursor-pointer border-b border-editor-light dark:border-editor-dark has-[:checked]:border-editor-dark dark:has-[:checked]:border-editor-light opacity-75 has-[:checked]:opacity-100">
78+
<input type="radio" id="editor_left_pane_code" name="editor_left_pane" value="codeEditor" class="hidden peer" checked>
7879
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
7980
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75 16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0 0 20.25 18V6A2.25 2.25 0 0 0 18 3.75H6A2.25 2.25 0 0 0 3.75 6v12A2.25 2.25 0 0 0 6 20.25Z" />
8081
</svg>
81-
<span>Code</span>
82+
<span class="ml-1">Code</span>
8283
</label>
8384
</div>
84-
<div target="editor" class="w-full h-full"></div>
85+
<div id="configEditor" data-pane="editor_left_pane" class="w-full h-full hidden"></div>
86+
<div id="codeEditor" data-pane="editor_left_pane" class="w-full h-full"></div>
8587
</div>
86-
<div class="flex flex-col w-1/2 relative">
87-
<div class="flex flex-row flex-grow text-sm border-slate-300">
88-
<label for="editor_right_pane_preview" class="border active p-1 flex flex-row space-x-1 items-center has-[:checked]:bg-editor-light dark:peer-checked:bg-editor-dark">
89-
<input type="radio" id="editor_right_pane_preview" name="editor_right_pane" value="preview" class="hidden" checked>
88+
<div id="editor_right_pane" class="flex flex-col w-1/2 relative">
89+
<div class="flex flex-row flex-grow text-sm bg-editor-light dark:bg-editor-dark">
90+
<label for="editor_right_pane_preview" class="p-1 mb-1 flex flex-row items-center cursor-pointer border-b border-editor-light dark:border-editor-dark has-[:checked]:border-editor-dark dark:has-[:checked]:border-editor-light opacity-75 has-[:checked]:opacity-100">
91+
<input type="radio" id="editor_right_pane_preview" name="editor_right_pane" value="previewEditor" class="hidden" checked>
9092
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
9193
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
9294
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
9395
</svg>
94-
<span>Preview</span>
96+
<span class="ml-1">Preview</span>
9597
</label>
96-
<label for="editor_right_pane_output" class="border p-1 flex flex-row space-x-1 items-center has-[:checked]:bg-editor-light dark:peer-checked:bg-editor-dark">
97-
<input type="radio" id="editor_right_pane_output" name="editor_right_pane" value="output" class="hidden">
98+
<label for="editor_right_pane_output" class="p-1 mb-1 flex flex-row items-center cursor-pointer border-b border-editor-light dark:border-editor-dark has-[:checked]:border-editor-dark dark:has-[:checked]:border-editor-light opacity-75 has-[:checked]:opacity-100">
99+
<input type="radio" id="editor_right_pane_output" name="editor_right_pane" value="outputEditor" class="hidden">
98100
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
99101
<path stroke-linecap="round" stroke-linejoin="round" d="m6.75 7.5 3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z" />
100102
</svg>
101-
<span>Output</span>
103+
<span class="ml-1">Output</span>
102104
</label>
103105
</div>
104-
<div target="preview" class="w-full h-full"></div>
106+
<div id="previewEditor" data-pane="editor_right_pane" class="w-full h-full"></div>
107+
<div id="outputEditor" data-pane="editor_right_pane" class="w-full h-full hidden"></div>
105108
</div>
106109
</div>
107110
</div>

src/vm.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,54 @@
11
import { DefaultRubyVM } from "@ruby/wasm-wasi/dist/browser";
2+
import { consolePrinter } from "@ruby/wasm-wasi";
23
import ruby from "./ruby.wasm";
34

45
export default async function initVM() {
56
const module = await ruby();
7+
8+
const output = [];
9+
const originalLog = console.log;
10+
window.$puts = function (val) {
11+
originalLog(val);
12+
output.push(val);
13+
};
14+
615
const { vm } = await DefaultRubyVM(module);
716

17+
window.$puts.flush = () => output.splice(0, output.length).join("\n");
18+
819
vm.eval(`
920
require "/bundle/setup"
1021
require "rubygems"
1122
# Make gem no-op
1223
define_singleton_method(:gem) { |*| nil }
1324
25+
require "js"
26+
27+
module Kernel
28+
def puts(val)
29+
JS.eval("window.$puts('#{val.inspect}')")
30+
nil
31+
end
32+
33+
def p(val)
34+
JS.eval("window.$puts('#{val}')")
35+
nil
36+
end
37+
end
38+
1439
require "ruby-next/language"
1540
require "ruby-next/language/rewriters/edge"
1641
require "ruby-next/language/rewriters/proposed"
42+
43+
module RubyNext
44+
def self.transform(code, version: RUBY_VERSION, using: false)
45+
options = {using:}
46+
options[:rewriters] = Language.rewriters.select { |rw| rw.unsupported_version?(version) }
47+
48+
source = Language.transform(code, **options)
49+
source += "\n # Transformed with RubyNext v#{RubyNext::VERSION} for Ruby #{version}"
50+
end
51+
end
1752
`);
1853

1954
return vm;

0 commit comments

Comments
 (0)