-
-
Notifications
You must be signed in to change notification settings - Fork 4
fixed using function as argument in lox interpreter #5
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
base: main
Are you sure you want to change the base?
Changes from 3 commits
d385b32
1a37376
226ded9
9ab48d6
7924271
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"cells":[{"kind":2,"language":"lox","value":"// So far fails with: No variable 'outside' defined\n\nfun returnFunction(): () => void {\n var outside = \"outside\";\n\n fun inner(): void {\n print outside;\n }\n\n return inner;\n}\n\nvar fn = returnFunction();\nfn();"},{"kind":2,"language":"lox","value":"// So far fails with: No variable 'exponent' defined\n\nfun power(exponent: number): (number) => number {\n\tfun applyPower(base: number): number {\n \tvar current = 1;\n for (var i = 0; i < exponent; i = i + 1) {\n \tcurrent = current * base;\n }\n return current;\n }\n return applyPower;\n}\n\nvar cube = power(3);\n\nprint cube(1);\nprint cube(2);\nprint cube(3);\nprint cube(4);\nprint cube(5);\n"}]} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"cells":[{"kind":2,"language":"lox","value":"fun returnSum(a: number, b: number): number {\n print (\"returnSum\");\n return a + b;\n}\n\n// Closures\n\nfun identity(a: (number, number) => number): (number, number) => number {\n print (\"identity\");\n return a;\n}\n\n// Calls identity with reference to function as argument, which is returned,\n// and apply (1, 2) on that function, which\n// Obviously identity is called before returnSum.\nprint identity(returnSum)(1, 2); \n\n// Calls returnSum, then identity is called with that return value as argument.\n// Obviously returnSum is called before identity.\n// print identity(returnSum(1, 2)); \n// But: This should fail, as the number return value is no valid argument for a: (number, number) => number !!!\n\n\n// var av = 1;\n// av();\n// Expected: Cannot call a non-function\n"},{"kind":2,"language":"lox","value":"fun aFunction(aLambda: (number) => number, aNumber: number): number {\n return aLambda(aNumber);\n}\n\nfun aTimesTwo(a: number): number {\n return a * 2;\n}\n\nvar result = aFunction(aTimesTwo, 9);\nprint result;"}]} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ 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 } from 'vscode-jsonrpc'; | ||
|
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. Was necessary to import less from vscode, otherwise the full initialization breaks unit test |
||
|
|
||
| export interface InterpreterContext { | ||
| log: (value: unknown) => MaybePromise<void> | ||
|
|
@@ -261,18 +261,18 @@ async function runMemberCall(memberCall: MemberCall, context: RunnerContext): Pr | |
| } | ||
|
|
||
| if (memberCall.explicitOperationCall) { | ||
| if (isFunctionDeclaration(ref)) { | ||
| if (isFunctionDeclaration(value)) { | ||
| const args = await Promise.all(memberCall.arguments.map(e => runExpression(e, context))); | ||
| context.variables.enter(); | ||
| const names = ref.parameters.map(e => e.name); | ||
| const names = value.parameters.map(e => e.name); | ||
| for (let i = 0; i < args.length; i++) { | ||
| context.variables.push(names[i], args[i]); | ||
| } | ||
| let functionValue: unknown; | ||
| const returnFn: ReturnFunction = (returnValue) => { | ||
| functionValue = returnValue; | ||
| } | ||
| await runLoxElement(ref.body, context, returnFn); | ||
| await runLoxElement(value.body, context, returnFn); | ||
| context.variables.leave(); | ||
| return functionValue; | ||
| } else { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| import { runInterpreter } from '../src/interpreter/runner.js'; | ||
| import { expect, test } from 'vitest'; | ||
|
|
||
| test('identity function', async() => { | ||
| const input = ` | ||
| fun returnSum(a: number, b: number): number { | ||
| print "returnSum called"; | ||
| return a + b; | ||
| } | ||
| fun identity(a: (number, number) => number): (number, number) => number { | ||
| print "identity called"; | ||
| return a; | ||
| } | ||
| print identity(returnSum)(27, 15); // prints "42"; | ||
| `; | ||
|
|
||
| const expectedOutput = ` | ||
| identity called | ||
| returnSum called | ||
| 42 | ||
| `; | ||
|
|
||
| await runInterpreterAndAssertOutput(input, expectedOutput); | ||
| }); | ||
|
|
||
| test('pass reference to function and call it', async() => { | ||
| const input = ` | ||
| fun aFunction(aLambda: (number) => number, aNumber: number): number { | ||
| print "aFunction called"; | ||
| return aLambda(aNumber); | ||
| } | ||
| fun aTimesTwo(a: number): number { | ||
| print "aTimeTwo called"; | ||
| return a * 2; | ||
| } | ||
| var result = aFunction(aTimesTwo, 9); | ||
| print result; | ||
| `; | ||
|
|
||
| const expectedOutput = ` | ||
| aFunction called | ||
| aTimeTwo called | ||
| 18 | ||
| `; | ||
|
|
||
| await runInterpreterAndAssertOutput(input, expectedOutput); | ||
| }); | ||
|
|
||
| test('Closure 1', async() => { | ||
| const input = ` | ||
| // So far fails with: No variable 'outside' defined | ||
|
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. maybe failing tests should be commented out and marked with a TODO or FIXME comment? |
||
| fun returnFunction(): () => void { | ||
| var outside = "outside"; | ||
| fun inner(): void { | ||
| print outside; | ||
| } | ||
| return inner; | ||
| } | ||
| var fn = returnFunction(); | ||
| fn(); | ||
| `; | ||
|
|
||
| const expectedOutput = ` | ||
| `; | ||
|
|
||
| await runInterpreterAndAssertOutput(input, expectedOutput); | ||
| }); | ||
|
|
||
| test('Closure 2', async() => { | ||
| const input = ` | ||
| // So far fails with: No variable 'exponent' defined | ||
| fun power(exponent: number): (number) => number { | ||
| fun applyPower(base: number): number { | ||
| var current = 1; | ||
| for (var i = 0; i < exponent; i = i + 1) { | ||
| current = current * base; | ||
| } | ||
| return current; | ||
| } | ||
| return applyPower; | ||
| } | ||
| var cube = power(3); | ||
| print cube(1); | ||
| print cube(2); | ||
| print cube(3); | ||
| print cube(4); | ||
| print cube(5); | ||
| `; | ||
|
|
||
| const expectedOutput = ` | ||
| `; | ||
|
|
||
| await runInterpreterAndAssertOutput(input, expectedOutput); | ||
| }); | ||
|
|
||
|
|
||
| async function runInterpreterAndAssertOutput(input: string, expectedOutput: string) { | ||
| // TODO call valication before ?!? | ||
| let output = ""; | ||
| await runInterpreter(input, { | ||
| log: value => { | ||
| output = output.concat(`${value}`); | ||
| } | ||
| }); | ||
| expect(output.replace(/\s/g, "")).toBe(expectedOutput.replace(/\s/g, "")); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import type { LoxProgram } from '../src/language-server/generated/ast.js'; | ||
| import { createLoxServices } from '../src/language-server/lox-module.js'; | ||
| import { EmptyFileSystem } from 'langium'; | ||
| import { parseHelper } from 'langium/test'; | ||
| import { test } from 'vitest'; | ||
|
|
||
|
|
||
| test('parse', async() => { | ||
| const services = createLoxServices(EmptyFileSystem).Lox; | ||
| const parse = parseHelper<LoxProgram>(services); | ||
|
|
||
| const input = ` | ||
| fun returnSum(a: number, b: number): number { | ||
| return a + b; | ||
| } | ||
| // Closures | ||
| fun identity(a: (number, number) => number): (number, number) => number { | ||
| return a; | ||
| } | ||
| print identity(returnSum)(1, 2); // prints "3"; | ||
| ` | ||
|
|
||
| const ast = await parse(input); | ||
| ast.parseResult; | ||
|
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. not yet a complete test ... |
||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "extends": "./tsconfig.json", | ||
| "compilerOptions": { | ||
| "rootDir": "src", | ||
| "outDir": "out" | ||
| }, | ||
| "include": [ | ||
| "src/**/*" | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "extends": "./tsconfig.src.json", | ||
| "compilerOptions": { | ||
| "noEmit": true, | ||
| "rootDir": "test" | ||
| }, | ||
| "references": [{ | ||
| "path": "./tsconfig.src.json" | ||
| }], | ||
| "include": [ | ||
| "test/**/*", | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { defineConfig } from 'vitest/config' | ||
|
|
||
| export default defineConfig({ | ||
| test: { | ||
| coverage: { | ||
| provider: 'v8', | ||
| reporter: ['text', 'html'], | ||
| include: ['src'], | ||
| exclude: ['**/generated'], | ||
| }, | ||
| deps: { | ||
| interopDefault: true | ||
| }, | ||
| include: ['test/*.test.ts'] | ||
| } | ||
| }) |
Uh oh!
There was an error while loading. Please reload this page.