-
Notifications
You must be signed in to change notification settings - Fork 37
Lox showcase (Fixed Commits) #197
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
Open
emilkrebs
wants to merge
14
commits into
main
Choose a base branch
from
loxShowcase
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
b41514a
added a lox showcase
emilkrebs 552d3ff
small ui fixes
emilkrebs 9e13c70
added beta banner
emilkrebs 994182f
Fix typo in getting-started grammar (#198)
VitGottwald 89ed9f2
Update Langium + Monaco Tutorials (#192)
montymxb 6237ca9
Update to latest monaco-editor-wrapper and monaco-editor-react (#196)
kaisalmen dc84df4
Minor DomainModel Showcase fixes (#200)
emilkrebs a05ddc6
edit package.json
emilkrebs 379f3c9
added a lox showcase
emilkrebs 09672ac
small ui fixes
emilkrebs 576e4dd
added beta banner
emilkrebs b2e02e9
edit package.json
emilkrebs 67ebaf4
Merge branch 'loxShowcase' of https://github.com/eclipse-langium/lang…
emilkrebs 456d546
splitted file
emilkrebs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| export interface LoxMessage { | ||
| type: LoxMessageType; | ||
| content: unknown; | ||
| }; | ||
|
|
||
| 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" } }, | ||
| ], | ||
| } | ||
| }; | ||
emilkrebs marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,257 @@ | ||
| import { | ||
| MonacoEditorReactComp, | ||
| } 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"; | ||
| import { UserConfig } from "monaco-editor-wrapper"; | ||
| import { createUserConfig } from "../utils"; | ||
|
|
||
| buildWorkerDefinition( | ||
| "../../libs/monaco-editor-workers/workers", | ||
| new URL("", window.location.href).href, | ||
| false | ||
| ); | ||
| let userConfig: UserConfig; | ||
|
|
||
| interface PreviewProps { | ||
| diagnostics?: Diagnostic[]; | ||
| } | ||
|
|
||
| interface PreviewState { | ||
| diagnostics?: Diagnostic[]; | ||
| messages: TerminalMessage[]; | ||
| } | ||
|
|
||
| interface TerminalMessage { | ||
| 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-x-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} | ||
| userConfig={userConfig} | ||
| 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()); | ||
| } | ||
|
|
||
| userConfig = createUserConfig({ | ||
| languageId: 'lox', | ||
| code: exampleCode, | ||
| htmlElement: document.getElementById('root')!, | ||
| worker: '/showcase/libs/worker/loxServerWorker.js', | ||
| monarchGrammar: syntaxHighlighting | ||
| }); | ||
| const root = createRoot(document.getElementById("root") as HTMLElement); | ||
| root.render(<App />); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| --- |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.