Skip to content

Commit

Permalink
[feat] Integrate nose runtime to playground
Browse files Browse the repository at this point in the history
  • Loading branch information
LeahHirst committed Nov 12, 2024
1 parent 4c98094 commit 1e57605
Show file tree
Hide file tree
Showing 17 changed files with 4,327 additions and 1,472 deletions.
12 changes: 10 additions & 2 deletions apps/site/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@
import { defineConfig } from 'astro/config';

import react from '@astrojs/react';
import partytown from '@astrojs/partytown';

// https://astro.build/config
export default defineConfig({
site: 'https://nospacelang.org',
integrations: [react()],
});
integrations: [
react(),
partytown({
config: {
forward: ['dataLayer.push'],
},
}),
],
});
1 change: 1 addition & 0 deletions apps/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"dependencies": {
"@astrojs/check": "^0.9.3",
"@astrojs/partytown": "^2.1.2",
"@astrojs/react": "^3.6.2",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
Expand Down
115 changes: 44 additions & 71 deletions apps/site/src/components/playground/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Editor as MonacoEditor, useMonaco } from '@monaco-editor/react';
import styled from '@emotion/styled';
import { NospaceIR } from '@repo/parser';
import Button from './Button';
import Dropdown, { DropdownAction } from './Dropdown';
import {
Expand All @@ -11,12 +10,14 @@ import {
} from '@repo/language-support';
import type { editor } from 'monaco-editor';
import { Typechecker } from '@repo/typecheck/index';
import { getProgram, serializeProgram } from './utils/program';

const Container = styled.div`
display: flex;
flex-direction: column;
height: 100%;
color: #c8b1db;
flex: 1;
`;

const Toolbar = styled.div`
Expand All @@ -40,32 +41,6 @@ Label Test
WriteInt
`;

function getProgram(lang: string, code: string) {
switch (lang) {
case 'Nospace':
return NospaceIR.fromNospace(code);
case 'Whitespace':
return NospaceIR.fromWhitespace(code);
case 'Nossembly':
return NospaceIR.fromNossembly(code);
default:
throw new Error('Unrecognized language');
}
}

function serializeProgram(lang: string, prog: NospaceIR) {
switch (lang) {
case 'Nospace':
return prog.toNospace();
case 'Whitespace':
return prog.toWhitespace();
case 'Nossembly':
return prog.toNossembly();
default:
throw new Error('Unrecognized language');
}
}

export default function Editor() {
const monaco = useMonaco();
const [language, setLanguage] = useState('Nospace');
Expand All @@ -85,43 +60,43 @@ export default function Editor() {
monaco.editor.setModelLanguage(editor.getModel()!, 'nospace');
}, [monaco]);

const highlightErrors = React.useCallback(
(lang: string) => {
if (!monaco) {
return;
}
const highlightErrors = React.useCallback(() => {
if (!monaco) {
return;
}

const editor = monaco.editor.getEditors()[0];
const editor = monaco.editor.getEditors()[0];

// Todo: debounce
const parsed = getProgram(lang, editor.getValue());
const markers: editor.IMarkerData[] = parsed.parseErrors.map((error) => ({
startLineNumber: error.meta.startLn + 1,
endLineNumber: error.meta.endLn + 1,
startColumn: error.meta.startCol + 1,
endColumn: error.meta.endCol + 1,
message: `ParseError: ${error.message}`,
severity: monaco.MarkerSeverity.Error,
}));

const [typechecked, typeErrors] = new Typechecker(parsed).typecheck();
if (!typechecked) {
for (const error of typeErrors.errors) {
markers.push({
startLineNumber: error.meta.startLn + 1,
endLineNumber: error.meta.endLn + 1,
startColumn: error.meta.startCol + 1,
endColumn: error.meta.endCol + 1,
message: `TypeError: ${error.message}`,
severity: monaco.MarkerSeverity.Error,
});
}
// Todo: debounce
const parsed = getProgram(
editor.getModel()?.getLanguageId() ?? 'nospace',
editor.getValue(),
);
const markers: editor.IMarkerData[] = parsed.parseErrors.map((error) => ({
startLineNumber: error.meta.startLn + 1,
endLineNumber: error.meta.endLn + 1,
startColumn: error.meta.startCol + 1,
endColumn: error.meta.endCol + 1,
message: `ParseError: ${error.message}`,
severity: monaco.MarkerSeverity.Error,
}));

const [typechecked, typeErrors] = new Typechecker(parsed).typecheck();
if (!typechecked) {
for (const error of typeErrors.errors) {
markers.push({
startLineNumber: error.meta.startLn + 1,
endLineNumber: error.meta.endLn + 1,
startColumn: error.meta.startCol + 1,
endColumn: error.meta.endCol + 1,
message: `TypeError: ${error.message}`,
severity: monaco.MarkerSeverity.Error,
});
}
}

monaco.editor.setModelMarkers(editor.getModel()!, 'owner', markers);
},
[monaco],
);
monaco.editor.setModelMarkers(editor.getModel()!, 'owner', markers);
}, [monaco]);

React.useEffect(() => {
if (!monaco) {
Expand All @@ -132,7 +107,7 @@ export default function Editor() {

const cleanup = editor.onDidChangeCursorPosition((e) => {
setCursorPos([e.position.lineNumber, e.position.column]);
highlightErrors(language);
highlightErrors();
});
return () => {
cleanup.dispose();
Expand All @@ -146,9 +121,17 @@ export default function Editor() {
}

const editor = monaco.editor.getEditors()[0];

monaco?.editor.setModelLanguage(editor?.getModel()!, lang.toLowerCase());
editor?.updateOptions({
autoIndent: lang === 'Nossembly' ? 'advanced' : 'none',
fontSize: lang === 'Nossembly' ? 16.0001 : 16, // horrible hack to force Monaco to update options immediately
});

const program = getProgram(language, editor.getValue());
editor.setValue(serializeProgram(lang, program));
setLanguage(lang);
highlightErrors();
},
[monaco, language],
);
Expand Down Expand Up @@ -200,17 +183,7 @@ export default function Editor() {
key={lang}
active={language === lang}
onClick={() => {
const editor = monaco?.editor.getEditors()[0];
monaco?.editor.setModelLanguage(
editor?.getModel()!,
lang.toLowerCase(),
);
editor?.updateOptions({
autoIndent: lang === 'Nossembly' ? 'advanced' : 'none',
fontSize: lang === 'Nossembly' ? 16.0001 : 16, // horrible hack to force Monaco to update options immediately
});
changeLanguage(lang);
highlightErrors(lang);
}}
>
{lang}
Expand Down
81 changes: 81 additions & 0 deletions apps/site/src/components/playground/EditorSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react';
import styled from '@emotion/styled';
import { usePlaygroundContext } from './PlaygroundContext';

const SidebarContainer = styled.div`
min-width: 500px;
background-color: #2e2e2e;
border-left: solid 1px #767676;
padding: 16px;
color: white;
display: flex;
flex-direction: column;
gap: 20px;
`;

const CodeBox = styled.code`
padding: 8px;
min-height: 16px;
background-color: #1e1e1e;
flex: 0;
`;

const getMessageColor = (type: 'error' | 'success' | 'warning') => {
switch (type) {
case 'success':
return '#8ce08c';
case 'error':
return '#e08c8c';
case 'warning':
return '#ffbf33';
}
};

const Message = styled.div<{ type: 'error' | 'success' | 'warning' }>`
color: ${({ type }) => getMessageColor(type)};
`;

const TextArea = styled.textarea`
background-color: #1e1e1e;
flex: 1;
color: white;
padding: 8px;
`;

const Field = styled.div<{ stretch?: boolean }>`
display: flex;
flex-direction: column;
gap: 12px;
flex: ${({ stretch }) => (stretch ? 1 : 0)};
`;

export const EditorSidebar = () => {
const { compilerOutput, programInput, setProgramInput, programOutput } =
usePlaygroundContext();

return (
<SidebarContainer>
<Field>
Compiler output
<CodeBox>
{compilerOutput.map((x, i) => (
<Message type={x.type} key={i}>
{x.message}
</Message>
))}
</CodeBox>
</Field>
<Field stretch>
Program input
<TextArea
value={programInput}
onChange={(e) => setProgramInput(e.target.value)}
></TextArea>
</Field>
<Field stretch>
Program output
<TextArea value={programOutput} readOnly></TextArea>
</Field>
</SidebarContainer>
);
};
5 changes: 4 additions & 1 deletion apps/site/src/components/playground/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import styled from '@emotion/styled';
import Dropdown, { DropdownAction } from './Dropdown';
import Button from './Button';
import { usePlaygroundContext } from './PlaygroundContext';

const Base = styled.nav`
background-color: #2e2e2e;
Expand Down Expand Up @@ -31,6 +32,8 @@ const RightActions = styled.div`
`;

export default function Header() {
const { run } = usePlaygroundContext();

return (
<Base>
<Flex>
Expand All @@ -41,7 +44,7 @@ export default function Header() {
</Dropdown>
</Flex>
<RightActions>
<Button>Run</Button>
<Button onClick={run}>Run</Button>
<Button>Share</Button>
</RightActions>
</Base>
Expand Down
21 changes: 17 additions & 4 deletions apps/site/src/components/playground/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react';
import styled from '@emotion/styled';
import Header from './Header';
import Editor from './Editor';
import { EditorSidebar } from './EditorSidebar';
import { PlaygroundContextProvider } from './PlaygroundContext';

const Container = styled.div`
display: flex;
Expand All @@ -11,11 +13,22 @@ const Container = styled.div`
background-color: #1e1e1e;
`;

const SplitView = styled.div`
display: flex;
width: 100%;
height: 100%;
`;

export default function Playground() {
return (
<Container>
<Header />
<Editor />
</Container>
<PlaygroundContextProvider>
<Container>
<Header />
<SplitView>
<Editor />
<EditorSidebar />
</SplitView>
</Container>
</PlaygroundContextProvider>
);
}
Loading

0 comments on commit 1e57605

Please sign in to comment.