summaryrefslogtreecommitdiff
path: root/src/toys/tabloid/js/playground.js
blob: d6ff71aa5bdfcf06282e70cf251c80aeaba09b98 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { tokenize, Parser, Environment } from "./tabloid.js";
import { SAMPLE_PROGRAMS } from "./samples.js";

class TabloidRunner {
  run(code) {
    const stdout = [];
    const stderr = [];

    if (!code) {
      stderr.push("No code to execute.");
      return { stdout, stderr };
    }

    try {
      const tokens = tokenize(code);
      const nodes = new Parser(tokens).parse();
      const env = new Environment({
        print: (msg) => stdout.push(String(msg)),
        input: (promptText) => window.prompt(promptText) ?? ""
      });
      env.run(nodes);
    } catch (error) {
      stderr.push(error.message || error.toString());
    }

    return { stdout, stderr };
  }
}

export class TabloidPlayground {
  constructor() {
    this.runner = new TabloidRunner();
    this.programLookup = new Map(SAMPLE_PROGRAMS.map((program) => [program.id, program]));
    this.activeProgramId = this.defaultProgramId = SAMPLE_PROGRAMS[0].id;

    this.stdoutElement = document.getElementById("stdout-content");
    this.errorElement = document.getElementById("error-content");
    this.programSelect = document.getElementById("program-select");
    this.runButton = document.getElementById("run-button");
    this.clearButton = document.getElementById("clear-button");

    this.editor = adelieEditor.init("#code-editor", {
      language: "tabloid"
    });

    this.init();
  }

  init() {
    this.populateProgramSelect();
    this.registerEvents();
    this.loadProgram(this.activeProgramId);
  }

  populateProgramSelect() {
    if (!this.programSelect) {
      return;
    }

    this.programSelect.innerHTML = "";
    SAMPLE_PROGRAMS.forEach((program) => {
      const option = document.createElement("option");
      option.value = program.id;
      option.textContent = program.label;
      this.programSelect.appendChild(option);
    });

    this.programSelect.value = this.activeProgramId;
  }

  registerEvents() {
    this.runButton.addEventListener("click", () => this.runCode());
    this.clearButton.addEventListener("click", () => this.resetOutput());

    this.programSelect.addEventListener("change", () => {
      this.loadProgram(this.programSelect.value);
    });

    document.addEventListener("keydown", (event) => {
      if (event.ctrlKey && event.key === "Enter") {
        event.preventDefault();
        this.runCode();
      }
    });
  }

  setEditorContent(content) {
    const length = this.editor.state.doc.toString().length;
    this.editor.dispatch({
      changes: { from: 0, to: length, insert: content }
    });
  }

  loadProgram(programId = this.defaultProgramId) {
    const selectedProgram = this.programLookup.get(programId);
    if (!selectedProgram) {
      return;
    }

    this.activeProgramId = selectedProgram.id;
    this.setEditorContent(selectedProgram.code);
    this.resetOutput();

    if (this.programSelect) {
      this.programSelect.value = this.activeProgramId;
    }
  }

  resetOutput() {
    this.stdoutElement.textContent = "";
    this.errorElement.textContent = "";
  }

  runCode() {
    const code = this.editor.state.doc.toString();
    const result = this.runner.run(code);
    this.stdoutElement.textContent = result.stdout.join("\n");
    this.errorElement.textContent = result.stderr.join("\n");
  }
}