-
-
Notifications
You must be signed in to change notification settings - Fork 4
Modified the language server so it sends the results of the interpreter #3
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
Changes from all commits
555ae8b
f5ddda0
755a267
b8e5b5f
e540702
2cc5f0d
7ca66b8
f335a06
ddd0ee2
4a928d6
a5396ab
7fad26c
a643beb
4c81da4
93b88cb
7ddaff1
bfd6a07
687b17c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,14 +3,18 @@ import { BinaryExpression, Expression, isBinaryExpression, isBooleanExpression, | |
| import { createLoxServices } from "../language-server/lox-module"; | ||
| import { v4 } from 'uuid'; | ||
| import { URI } from "vscode-uri"; | ||
| import { CancellationToken } from "vscode-languageclient"; | ||
| import { CancellationToken, CancellationTokenSource } from "vscode-languageserver"; | ||
|
|
||
| export interface InterpreterContext { | ||
| log: (value: unknown) => MaybePromise<void> | ||
| log: (value: unknown) => MaybePromise<void>, | ||
| onStart?: () => void, | ||
| } | ||
|
|
||
| const services = createLoxServices(EmptyFileSystem); | ||
|
|
||
| // after 5 seconds, the interpreter will be interrupted and call onTimeout | ||
| const TIMEOUT_MS = 1000 * 5; | ||
|
|
||
| export async function runInterpreter(program: string, context: InterpreterContext): Promise<void> { | ||
| const buildResult = await buildDocument(program); | ||
| try { | ||
|
|
@@ -25,7 +29,10 @@ type ReturnFunction = (value: unknown) => void; | |
|
|
||
| interface RunnerContext { | ||
| variables: Variables, | ||
| log: (value: unknown) => MaybePromise<void> | ||
| cancellationToken: CancellationToken, | ||
| timeout: NodeJS.Timeout, | ||
| log: (value: unknown) => MaybePromise<void>, | ||
| onStart?: () => void, | ||
| } | ||
|
|
||
| class Variables { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think and suggest that you should avoid using UNKNOWN, because UNKNOWN cannot just be a primitive. PLUS, you can add more metadata if needed by trying this pattern: 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)
}There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe as separated PR, I see it is everywhere O.o...
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, maybe that would be better. What do you think @msujew? |
||
|
|
@@ -88,17 +95,38 @@ async function buildDocument(program: string): Promise<BuildResult> { | |
| } | ||
| } | ||
|
|
||
| async function runProgram(program: LoxProgram, outerContext: InterpreterContext): Promise<void> { | ||
| export async function runProgram(program: LoxProgram, outerContext: InterpreterContext): Promise<void> { | ||
| const cancellationTokenSource = new CancellationTokenSource(); | ||
| const cancellationToken = cancellationTokenSource.token; | ||
|
|
||
| const timeout = setTimeout(async () => { | ||
| cancellationTokenSource.cancel(); | ||
| }, TIMEOUT_MS); | ||
|
|
||
| const context: RunnerContext = { | ||
| variables: new Variables(), | ||
| log: outerContext.log | ||
| cancellationToken, | ||
| timeout, | ||
| log: outerContext.log, | ||
| onStart: outerContext.onStart, | ||
| }; | ||
| context.variables.enter(); | ||
|
|
||
| let end = false; | ||
|
|
||
| context.variables.enter(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: Coding style: It is normally a good habit to surround such pair calls like |
||
| if (context.onStart) { | ||
| context.onStart(); | ||
| } | ||
|
|
||
| for (const statement of program.elements) { | ||
| await interruptAndCheck(context.cancellationToken); | ||
|
|
||
| if (!isClass(statement) && !isFunctionDeclaration(statement)) { | ||
| await runLoxElement(statement, context, () => { end = true }); | ||
| } | ||
| else if (isClass(statement)) { | ||
| throw new AstNodeError(statement, 'Classes are currently unsupported'); | ||
| } | ||
| if (end) { | ||
| break; | ||
| } | ||
|
|
@@ -107,6 +135,8 @@ async function runProgram(program: LoxProgram, outerContext: InterpreterContext) | |
| } | ||
|
|
||
| async function runLoxElement(element: LoxElement, context: RunnerContext, returnFn: ReturnFunction): Promise<void> { | ||
| await interruptAndCheck(context.cancellationToken); | ||
|
|
||
| if (isExpressionBlock(element)) { | ||
| await interruptAndCheck(CancellationToken.None); | ||
| context.variables.enter(); | ||
|
|
@@ -164,6 +194,9 @@ async function runLoxElement(element: LoxElement, context: RunnerContext, return | |
| } | ||
|
|
||
| async function runExpression(expression: Expression, context: RunnerContext): Promise<unknown> { | ||
| await interruptAndCheck(context.cancellationToken); | ||
|
|
||
|
|
||
| if (isBinaryExpression(expression)) { | ||
| const { left, right, operator } = expression; | ||
| const rightValue = await runExpression(right, context); | ||
|
|
@@ -244,6 +277,8 @@ async function setExpressionValue(left: Expression, right: unknown, context: Run | |
| } | ||
|
|
||
| async function runMemberCall(memberCall: MemberCall, context: RunnerContext): Promise<unknown> { | ||
| await interruptAndCheck(context.cancellationToken); | ||
|
|
||
| let previous: unknown = undefined; | ||
| if (memberCall.previous) { | ||
| previous = await runExpression(memberCall.previous, context); | ||
|
|
@@ -255,7 +290,7 @@ async function runMemberCall(memberCall: MemberCall, context: RunnerContext): Pr | |
| } else if (isVariableDeclaration(ref) || isParameter(ref)) { | ||
| value = context.variables.get(memberCall, ref.name); | ||
| } else if (isClass(ref)) { | ||
| throw new AstNodeError(memberCall, 'Classes are current unsupported'); | ||
| throw new AstNodeError(memberCall, 'Classes are currently unsupported'); | ||
| } else { | ||
| value = previous; | ||
| } | ||
|
|
@@ -317,6 +352,7 @@ function applyOperator(node: BinaryExpression, operator: string, left: unknown, | |
| } | ||
| } | ||
|
|
||
|
|
||
| function isNumber(value: unknown): value is number { | ||
| return typeof value === 'number'; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| /****************************************************************************** | ||
| * Copyright 2022 TypeFox GmbH | ||
| * This program and the accompanying materials are made available under the | ||
| * terms of the MIT License, which is available in the project root. | ||
| ******************************************************************************/ | ||
|
|
||
| import { startLanguageServer, EmptyFileSystem, DocumentState, LangiumDocument, OperationCancelled } from 'langium'; | ||
| import { BrowserMessageReader, BrowserMessageWriter, Diagnostic, NotificationType, createConnection } from 'vscode-languageserver/browser'; | ||
| import { runInterpreter } from '../interpreter/runner'; | ||
| import { createLoxServices } from './lox-module'; | ||
|
|
||
| declare const self: DedicatedWorkerGlobalScope; | ||
|
|
||
| /* browser specific setup code */ | ||
| const messageReader = new BrowserMessageReader(self); | ||
| const messageWriter = new BrowserMessageWriter(self); | ||
|
|
||
| const connection = createConnection(messageReader, messageWriter); | ||
|
|
||
| // Inject the shared services and language-specific services | ||
| const { shared } = createLoxServices({ connection, ...EmptyFileSystem }); | ||
|
|
||
| // Start the language server with the shared services | ||
| startLanguageServer(shared); | ||
|
|
||
| // Send a notification with the serialized AST after every document change | ||
| type DocumentChange = { uri: string, content: string, diagnostics: Diagnostic[] }; | ||
| const documentChangeNotification = new NotificationType<DocumentChange>('browser/DocumentChange'); | ||
|
|
||
|
|
||
| shared.workspace.DocumentBuilder.onBuildPhase(DocumentState.Validated, async documents => { | ||
| for (const document of documents) { | ||
| if (document.diagnostics === undefined || document.diagnostics.filter((i) => i.severity === 1).length === 0) { | ||
| try { | ||
| await runInterpreter(document.textDocument.getText(), { | ||
| log: (message) => { | ||
| sendMessage(document, "output", message); | ||
| }, | ||
| onStart: () => { | ||
| sendMessage(document, "notification", "startInterpreter"); | ||
| }, | ||
| }); | ||
| } catch (e: any) { | ||
| if (e === OperationCancelled) { | ||
| sendMessage(document, "error", "Interpreter timed out"); | ||
| } else { | ||
| sendMessage(document, "error", e.message); | ||
| } | ||
| } finally { | ||
| sendMessage(document, "notification", "endInterpreter"); | ||
| } | ||
|
|
||
| } | ||
| else { | ||
| sendMessage(document, "error", document.diagnostics) | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| function sendMessage(document: LangiumDocument, type: string, content: unknown): void { | ||
| connection.sendNotification(documentChangeNotification, { | ||
| uri: document.uri.toString(), | ||
| content: JSON.stringify({ type, content }), | ||
| diagnostics: document.diagnostics ?? [] | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // Monarch syntax highlighting for the lox language. | ||
| export default { | ||
| 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"} }, | ||
| ], | ||
| } | ||
| }; |
Uh oh!
There was an error while loading. Please reload this page.