Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lox Showcase #162

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
eedeebf
fixed showcased + added lox showcase
Jul 8, 2023
613f2ab
changed the lox showcase description
Jul 9, 2023
f6dd30a
fixed tailwind and added error handling to lox
Jul 10, 2023
a4c91ae
added a beta tag
Jul 12, 2023
a97fbca
oger
Jul 12, 2023
105b566
extended the lox example
Jul 12, 2023
308d1f0
fixed beta tag
Jul 17, 2023
5fee4ef
added autoscroll
Jul 17, 2023
23ca2fe
added debugging code
Jul 17, 2023
ebf8a84
removed autoscroll
Jul 18, 2023
3a9437c
added autoscroll (again)
Jul 18, 2023
fb8abda
removed debugging stuff from lox
Jul 18, 2023
a365a85
cleaned overflow-hidden
Aug 7, 2023
9de599f
squash and rebase
Oct 12, 2023
568a5e1
fixed showcased + added lox showcase
Jul 8, 2023
1e5beba
changed the lox showcase description
Jul 9, 2023
9e4f0a3
fixed tailwind and added error handling to lox
Jul 10, 2023
37af827
added a beta tag
Jul 12, 2023
aee2c7c
oger
Jul 12, 2023
1686025
extended the lox example
Jul 12, 2023
a5e0931
fixed beta tag
Jul 17, 2023
e06afd6
added autoscroll
Jul 17, 2023
7bf2248
added debugging code
Jul 17, 2023
772f9d7
removed autoscroll
Jul 18, 2023
bb01cf3
added autoscroll (again)
Jul 18, 2023
12680dd
removed debugging stuff from lox
Jul 18, 2023
4806fed
cleaned overflow-hidden
Aug 7, 2023
215359d
squash and rebase
Oct 12, 2023
2d757bc
Merge branch 'addLoxShowcase' of https://github.com/eclipse-langium/l…
Oct 12, 2023
57a150d
package-lock.json
Oct 12, 2023
47c22d0
fixed showcased + added lox showcase
Jul 8, 2023
ae68335
added a beta tag
Jul 12, 2023
77b3908
oger
Jul 12, 2023
fbce6a5
squash and rebase
Oct 12, 2023
11271e9
package-lock.json
Oct 12, 2023
0a7d50c
Merge branch 'addLoxShowcase' of https://github.com/eclipse-langium/l…
Oct 12, 2023
42f40be
fixed *-lock.json
Oct 12, 2023
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
2 changes: 1 addition & 1 deletion hugo/assets/scripts/arithmetics/arithmetics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class App extends React.Component<{}, AppState> {
<div className="border-solid border border-emeraldLangium bg-emeraldLangiumDarker flex items-center p-3 text-white font-mono ">
Preview
</div>
<div className="border border-emeraldLangium h-full w-full">
<div className="border border-emeraldLangium h-full w-full overflow-hidden overflow-y-scroll">
emilkrebs marked this conversation as resolved.
Show resolved Hide resolved
<Preview ref={this.preview} focusLine={(line: number) => {
this.monacoEditor.current?.getEditorWrapper()?.getEditor()?.revealLineInCenter(line);
this.monacoEditor.current?.getEditorWrapper()?.getEditor()?.setPosition({ lineNumber: line, column: 1 });
Expand Down
72 changes: 72 additions & 0 deletions hugo/assets/scripts/lox/lox-tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
export interface LoxMessage {
type: LoxMessageType;
content: unknown;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having unknown type is not so nice. There is a easy pattern I use:

type XType = 'A'|'B'|'C';
interface XBase {
  type: XType;
}

interface A extends XBase {
  type: 'A'; // subset of parent definition!
  content: string;
}

interface B extends XBase {
  type: 'B'; // subset of parent...
  content: number;
}

interface C extends XBase {
  type: 'C';
  content: boolean;
}
type X = A|B|C;

//usage: you can do stuff like this:
const x: X = ... //get from somewhere
if(x.type === 'A') {
   //it asserts then that x is of type A, so accessing x.content will be a string, no casts needed!!!
} else if(x.type === 'B') {
  //...x.content is number
} else if(x.type === 'C') {
  //...x.content is boolean
} else {
  assertUnreachable(x.type); //this code is then unreachable (it will be highlighted red when another type D was added)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interpreter returns an unknown type, so I could do something like this:

export interface LoxMessage {
    type: LoxMessageType;
    content: MessageContent;
};

type MessageContent = string | number | boolean | null | any;

};

export type LoxMessageType = "notification" | "output" | "error";


export const exampleCode = `fun factorial(n: number): number {
if (n <= 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}

fun binomial(n: number, k: number): number {
return factorial(n) / (factorial(k) * factorial(n - k));
}

fun pow(x: number, n: number): number {
var result = 1;
for (var i = 0; i < n; i = i + 1) {
result = result * x;
}
return result;
}

fun mod(x: number, y: number): number {
return x - y * (x / y);
}

fun floor(x: number): number {
return x - mod(x, 1);
}

print("factorial(5) = " + factorial(5));
print("binomial(5, 2) = " + binomial(5, 2));
print("pow(2, 10) = " + pow(2, 10));
print("mod(10, 3) = " + mod(10, 3));
print("floor(3.14) = " + floor(3.14));
`;

export const syntaxHighlighting = {
keywords: [
'and', 'boolean', 'class', 'else', 'false', 'for', 'fun', 'if', 'nil', 'number', 'or', 'print', 'return', 'string', 'super', 'this', 'true', 'var', 'void', 'while'
],
operators: [
'-', ',', ';', ':', '!', '!=', '.', '*', '/', '+', '<', '<=', '=', '==', '=>', '>', '>='
],
symbols: /-|,|;|:|!|!=|\.|\(|\)|\{|\}|\*|\+|<|<=|=|==|=>|>|>=/,

tokenizer: {
initial: [
{ regex: /[_a-zA-Z][\w_]*/, action: { cases: { '@keywords': { "token": "keyword" }, '@default': { "token": "ID" } } } },
{ regex: /[0-9]+(\.[0-9]+)?/, action: { "token": "number" } },
{ regex: /"[^"]*"/, action: { "token": "string" } },
{ include: '@whitespace' },
{ regex: /@symbols/, action: { cases: { '@operators': { "token": "operator" }, '@default': { "token": "" } } } },
],
whitespace: [
{ regex: /\s+/, action: { "token": "white" } },
{ regex: /\/\*/, action: { "token": "comment", "next": "@comment" } },
{ regex: /\/\/[^\n\r]*/, action: { "token": "comment" } },
],
comment: [
{ regex: /[^\/\*]+/, action: { "token": "comment" } },
{ regex: /\*\//, action: { "token": "comment", "next": "@pop" } },
{ regex: /[\/\*]/, action: { "token": "comment" } },
],
}
};
255 changes: 255 additions & 0 deletions hugo/assets/scripts/lox/lox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import {
MonacoEditorReactComp,
addMonacoStyles,
} from "@typefox/monaco-editor-react/bundle";
import { buildWorkerDefinition } from "monaco-editor-workers";
import React, { createRef, useRef } from "react";
import { createRoot } from "react-dom/client";
import { Diagnostic, DocumentChangeResponse } from "../langium-utils/langium-ast";
import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from "lz-string";
import { LoxMessage, exampleCode, syntaxHighlighting } from "./lox-tools";

buildWorkerDefinition(
"../../libs/monaco-editor-workers/workers",
new URL("", window.location.href).href,
false
);
addMonacoStyles("monaco-editor-styles");

interface PreviewProps {
diagnostics?: Diagnostic[];
}

interface PreviewState {
diagnostics?: Diagnostic[];
messages: TerminalMessage[];
}

interface TerminalMessage {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the same trick as above to avoid Union type for content

type: "notification" | "error" | "output";
content: string | string[];
}

class Preview extends React.Component<PreviewProps, PreviewState> {
terminalContainer: React.RefObject<HTMLDivElement>;
constructor(props: PreviewProps) {
super(props);
this.state = {
diagnostics: props.diagnostics,
messages: [],
};

this.terminalContainer = createRef<HTMLDivElement>();
}

println(text: string) {
this.setState((state) => ({
messages: [...state.messages, { type: "output", content: text }],
}));
}

error(text: string) {
this.setState((state) => ({
messages: [...state.messages, { type: "error", content: text }],
}));
}

clear() {
this.setState({ messages: [] });
}

setDiagnostics(diagnostics: Diagnostic[]) {
this.setState({ diagnostics: diagnostics });

}

render() {
// if the code doesn't contain any errors and the diagnostics aren't warnings
if (this.state.diagnostics == null || this.state.diagnostics.filter((i) => i.severity === 1).length == 0) {

// auto scroll to bottom
const terminal = this.terminalContainer.current;
const newLine = terminal?.lastElementChild;
if (newLine && terminal) {
const rect = newLine.getBoundingClientRect();
if (rect.bottom <= terminal.getBoundingClientRect().bottom) {
newLine.scrollIntoView();
}
}

return (
<div>
<div className="text-sm flex flex-col p-4 overflow-hidden overflow-y-scroll" ref={this.terminalContainer}>
{this.state.messages.map((message, index) =>
<p key={index} className={message.type == "error" ? "text-base text-accentRed" : "text-white"}>{message.type == "error" ? "An error occurred: " : ""} {message.content}</p>
)}
</div>
</div>
);
}

// Show the exception
return (
<div className="flex flex-col h-full w-full p-4 justify-start items-center my-10" >
<div className="text-white border-2 border-solid border-accentRed rounded-md p-4 text-left text-sm cursor-default">
{this.state.diagnostics.filter((i) => i.severity === 1).map((diagnostic, index) =>
<details key={index}>
<summary>{`Line ${diagnostic.range.start.line}-${diagnostic.range.end.line}: ${diagnostic.message}`}</summary>
<p>Source: {diagnostic.source} | Code: {diagnostic.code}</p>
</details>
)}
</div>
</div>
);
}
}



class App extends React.Component<{}, {}> {
monacoEditor: React.RefObject<MonacoEditorReactComp>;
preview: React.RefObject<Preview>;
copyHint: React.RefObject<HTMLDivElement>;
shareButton: React.RefObject<HTMLImageElement>;
constructor(props) {
super(props);

// bind 'this' ref for callbacks to maintain parent context
this.onMonacoLoad = this.onMonacoLoad.bind(this);
this.onDocumentChange = this.onDocumentChange.bind(this);
this.copyLink = this.copyLink.bind(this);
this.monacoEditor = React.createRef();
this.preview = React.createRef();
this.copyHint = React.createRef();
this.shareButton = React.createRef();
}

/**
* Callback that is invoked when Monaco is finished loading up.
* Can be used to safely register notification listeners, retrieve data, and the like
*
* @throws Error on inability to ref the Monaco component or to get the language client
*/
onMonacoLoad() {
// verify we can get a ref to the editor
if (!this.monacoEditor.current) {
throw new Error("Unable to get a reference to the Monaco Editor");
}

// verify we can get a ref to the language client
const lc = this.monacoEditor.current
?.getEditorWrapper()
?.getLanguageClient();
if (!lc) {
throw new Error("Could not get handle to Language Client on mount");
}
this.monacoEditor.current.getEditorWrapper()?.getEditor()?.focus();
// register to receive DocumentChange notifications
lc.onNotification("browser/DocumentChange", this.onDocumentChange);
}

/**
* Callback invoked when the document processed by the LS changes
* Invoked on startup as well
* @param resp Response data
*/
onDocumentChange(resp: DocumentChangeResponse) {
// decode the received Asts
const message = JSON.parse(resp.content) as LoxMessage;
switch (message.type) {
case "notification":
switch (message.content) {
case "startInterpreter":
this.preview.current?.clear();
break;
}
break;
case "error":
this.preview.current?.error(message.content as string);
break;
case "output":
this.preview.current?.println(message.content as string);
break;
}
this.preview.current?.setDiagnostics(resp.diagnostics);
}


async copyLink() {
const code = this.monacoEditor.current?.getEditorWrapper()?.getEditor()?.getValue()!;
const url = new URL("/showcase/lox", window.origin);
url.searchParams.append("code", compressToEncodedURIComponent(code));

this.copyHint.current!.style.display = "block";
this.shareButton.current!.src = '/assets/checkmark.svg';
setTimeout(() => {
this.shareButton.current!.src = '/assets/share.svg';
this.copyHint.current!.style.display = 'none';
}, 1000);

navigator.clipboard.writeText(window.location.href);

await navigator.clipboard.writeText(url.toString());
}

componentDidMount() {
this.shareButton.current!.addEventListener('click', this.copyLink);
}

render() {
const style = {
height: "100%",
width: "100%",
};
const url = new URL(window.location.toString());
let code = url.searchParams.get("code");
if (code) {
code = decompressFromEncodedURIComponent(code);
}

return (
<div className="justify-center self-center flex flex-col md:flex-row h-full w-full">
<div className="float-left w-full h-full flex flex-col">
<div className="border-solid border border-emeraldLangium bg-emeraldLangiumDarker flex items-center p-3 text-white font-mono ">
<span>Editor</span>
<div className="flex flex-row justify-end w-full h-full gap-2">
<div className="text-sm hidden" ref={this.copyHint}>Link was copied!</div>
<img src="/assets/share.svg" title="Copy URL to this grammar and content" className="inline w-4 h-4 cursor-pointer" ref={this.shareButton}></img>
</div>
</div>
<div className="wrapper relative bg-white dark:bg-gray-900 border border-emeraldLangium h-full w-full">
<MonacoEditorReactComp
ref={this.monacoEditor}
onLoad={this.onMonacoLoad}
webworkerUri="../showcase/libs/worker/loxServerWorker.js"
workerName="LS"
workerType="classic"
languageId="lox"
text={code ? code : exampleCode}
syntax={syntaxHighlighting}
style={style}
/>
</div>
</div>
<div className="float-left w-full h-full flex flex-col" id="preview">
<div className="border-solid border border-emeraldLangium bg-emeraldLangiumDarker flex items-center p-3 text-white font-mono ">
<span>Output</span>
</div>
<div className="border border-emeraldLangium h-full w-full overflow-hidden overflow-y-scroll">
<Preview ref={this.preview} />
</div>
</div>
</div>
);
}
}

export async function share(code: string): Promise<void> {
const url = new URL("/showcase/lox", window.origin);
url.searchParams.append("code", compressToEncodedURIComponent(code));
await navigator.clipboard.writeText(url.toString());
}


const root = createRoot(document.getElementById("root") as HTMLElement);
root.render(<App />);
2 changes: 1 addition & 1 deletion hugo/assets/scripts/statemachine/statemachine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ class App extends React.Component<{}> {
<div className="border-solid border border-emeraldLangium bg-emeraldLangiumDarker flex items-center p-3 text-white font-mono ">
Preview
</div>
<div className="border border-emeraldLangium h-full w-full">
<div className="border border-emeraldLangium h-full w-full overflow-hidden overflow-y-scroll">
<Preview ref={this.preview} />
</div>
</div>
Expand Down
14 changes: 14 additions & 0 deletions hugo/content/showcase/lox.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
title: "Lox"
weight: 500
type: langium
layout: showcase-page
url: "/showcase/lox"
img: "/assets/Langium_Lox.svg"
file: "scripts/lox/lox.tsx"
description: A tree-walk interpreter for the Lox language. It is based on the book 'Crafting Interpreters' by Bob Nystrom.
geekdochidden: true
draft: false
beta: true
noMain: true
---
6 changes: 5 additions & 1 deletion hugo/layouts/langium/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
<div class="rounded-lg border shadow-md bg-gray-800 border-gray-600 w-[300px] overflow-hidden self-stretch text-white">
<a href="{{.Params.url}}" class="self-stretch">
<img src="{{.Params.img}}" class="object-fill min-h-[300px]"/>
<h2 class="text-center text-xl mt-10 mb-5">{{.Title}}</h2>
<h2 class="flex flex-row items-center justify-center text-center text-xl mt-10 mb-5 gap-2">{{.Title}}
{{if .Params.beta}}
<div class="flex flex-row items-center justify-center border border-emeraldLangium p-1 rounded-xl text-xs text-emeraldLangium" title="This showcase is still in progress and may include some errors.">Beta</div>
Lotes marked this conversation as resolved.
Show resolved Hide resolved
{{ end }}
</h2>
<p class="line-clamp-[8] px-5 mb-10 text-sm">
{{.Params.description}}
</p>
Expand Down
3 changes: 2 additions & 1 deletion hugo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
"copy:monaco-workers": "npm run copy:libs:prepare && npx shx cp -f ../node_modules/monaco-editor-workers/dist/index.* ./static/libs/monaco-editor-workers && npx shx cp -f ../node_modules/monaco-editor-workers/dist/workers/editorWorker-iife.js ./static/libs/monaco-editor-workers/workers",
"build:worker/statemachine": "esbuild ../node_modules/langium-statemachine-dsl/out/language-server/main-browser.js --bundle --format=iife --outfile=./static/showcase/libs/worker/statemachineServerWorker.js",
"build:worker/arithmetics": "esbuild ../node_modules/langium-arithmetics-dsl/out/language-server/main-browser.js --bundle --format=iife --outfile=./static/showcase/libs/worker/arithmeticsServerWorker.js",
"build:worker/lox": "esbuild ../node_modules/langium-lox/out/language-server/main-browser.js --bundle --format=iife --outfile=./static/showcase/libs/worker/loxServerWorker.js",
"build:worker/playground-langium": "esbuild ./content/playground/langium-worker.ts --bundle --format=iife --outfile=./static/playground/libs/worker/langiumServerWorker.js",
"build:worker/playground-user": "esbuild ./content/playground/user-worker.ts --bundle --format=iife --outfile=./static/playground/libs/worker/userServerWorker.js",
"build:worker/playground-common": "esbuild ./content/playground/common.ts --bundle --format=esm --outfile=./static/playground/libs/worker/common.js",
"build:worker/sql": "esbuild ./assets/scripts/sql/language-server.ts --bundle --format=iife --outfile=./static/showcase/libs/worker/sqlServerWorker.js",
"build:worker/minilogo": "esbuild ../node_modules/langium-minilogo/out/language-server/main-browser.js --bundle --format=iife --outfile=./static/showcase/libs/worker/minilogoServerWorker.js",
"build:static": "npm run clean:static && npm run build:worker/statemachine && npm run build:worker/sql && npm run build:worker/minilogo && npm run build:worker/arithmetics && npm run build:worker/playground-common && npm run build:worker/playground-langium && npm run build:worker/playground-user && npm run copy:monaco-editor-wrapper && npm run copy:monaco-workers",
"build:static": "npm run clean:static && npm run build:worker/statemachine && npm run build:worker/lox && npm run build:worker/sql && npm run build:worker/minilogo && npm run build:worker/arithmetics && npm run build:worker/playground-common && npm run build:worker/playground-langium && npm run build:worker/playground-user && npm run copy:monaco-editor-wrapper && npm run copy:monaco-workers",
"build": "npm run build:static && cross-env NODE_ENV=production hugo --config ./config.toml -b / -d ../public --gc --minify ",
"watch": "npm run build:static && cross-env NODE_ENV=development hugo server --config ./config.toml -D -b localhost:1313 -d ../public --appendPort=false",
"watch:gitpod": "npm run build:static && cross-env NODE_ENV=development hugo server --config ./config.toml -D -b `gp url 1313` -d ../public --appendPort=false"
Expand Down
Loading