import * as React from "react"; import * as IR from "../../ir"; import * as Compiler from "../../compiler"; import { BootedCrochet, CrochetValue } from "../../crochet"; import { unreachable } from "../../utils/utils"; import { AddButton, Button, Column, Icon, IElement, If } from "./ui/basic"; import { CodeEditor } from "./ui/code-editor"; import { Value } from "./ui/value"; import { CrochetModule, Environment, Environments } from "../../vm"; type IPlayground = { crochet: BootedCrochet; module: CrochetModule; }; type IPlaygroundState = { editors: { id: number; editor: React.ReactNode }[]; }; export class PlaygroundUI extends React.Component< IPlayground, IPlaygroundState > { private id = 0; private next_id() { return ++this.id; } constructor(props: IPlayground) { super(props); this.state = { editors: [this.make_editor()], }; } render() { return (
{this.state.editors.map((a) => a.editor)}
); } make_editor() { const id = this.next_id(); return { id, editor: ( this.removeEditor(id)} /> ), }; } addEditor = () => { this.setState({ editors: [...this.state.editors, this.make_editor()], }); }; removeEditor = (id: number) => { this.setState({ editors: this.state.editors.filter((e) => e.id !== id) }); }; } class PEOk { constructor(readonly value: CrochetValue) {} } class PEFail { constructor(readonly error: any) {} } class PEMessage { constructor(readonly message: React.ReactNode) {} } type PEResult = PEOk | PEFail | PEMessage; type PEProps = IElement & { code: string; crochet: BootedCrochet; module: CrochetModule; dispose?: () => void; }; type PEState = { code: string; result: PEResult | null; changed: boolean; running: boolean; }; export class PlaygroundEditor extends React.Component { readonly editor = React.createRef(); constructor(props: PEProps) { super(props); this.state = { code: "", result: null, changed: false, running: false, }; } codeChanged = (code: string) => { if (this.state.result != null && code !== this.state.code) { this.setState({ changed: true }); } }; remove = () => { this.props.dispose?.(); }; runCode = async () => { try { this.setState({ running: true }); const code = this.editor.current!.code; const ast = Compiler.parse_repl(code, "(repl)"); const ir = Compiler.lower_to_repl(code, ast); switch (ir.tag) { case IR.ReplTag.DECLARATIONS: { for (const x of ir.declarations) { await this.props.crochet.load_declaration(x, this.props.module); } this.setState({ result: new PEMessage("Ok"), changed: false, running: false, }); break; } case IR.ReplTag.STATEMENTS: { const env = new Environment(null, null, this.props.module, null); const value = await this.props.crochet.run_block(ir.block, env); this.setState({ result: new PEOk(value), changed: false, running: false, }); } } } catch (error) { if (error instanceof SyntaxError) { this.setState({ result: new PEFail(error.message), changed: false, running: false, }); } else { this.setState({ result: new PEFail(error), changed: false, running: false, }); } } }; render() { return (
{this.renderResult()}
); } renderResult() { const result = this.state.result; if (result == null) { return null; } else { return (
{this.renderSpecificResult(result)}
); } } renderSpecificResult(result: PEResult) { if (result instanceof PEOk) { return (
); } else if (result instanceof PEFail) { const message = result.error instanceof Error ? result.error.stack ?? result.error.message : String(result.error); return (
{message}
); } else if (result instanceof PEMessage) { return (
{result.message}
); } else { throw unreachable(result, "Result"); } } }