From 8172f0218e17e18a80535223c6b97beda195f6ee Mon Sep 17 00:00:00 2001 From: Jeremy Walker Date: Thu, 23 Jan 2025 15:06:49 +0000 Subject: [PATCH] Function call desc (#7372) * Add function call descriptions * Fix bug * Fix tests * Detail with missing results * Guard against errors * Print out env to test if it works --- app/javascript/interpreter/expression.ts | 16 +- app/javascript/interpreter/frames.ts | 143 ++++++++++-------- .../expression_descriptions.test.ts | 42 ++++- .../statement_descriptions.test.ts | 119 +++++++++++++++ 4 files changed, 257 insertions(+), 63 deletions(-) diff --git a/app/javascript/interpreter/expression.ts b/app/javascript/interpreter/expression.ts index 6bc5e7c8dc..bde8a27bd3 100644 --- a/app/javascript/interpreter/expression.ts +++ b/app/javascript/interpreter/expression.ts @@ -1,7 +1,10 @@ import type { Token } from './token' import { Location } from './location' import { FrameWithResult } from './frames' -import { EvaluationResult } from './evaluation-result' +import { + EvaluationResult, + EvaluationResultCallExpression, +} from './evaluation-result' function quoteLiteral(value: any): string { if (typeof value === 'string') { @@ -42,6 +45,17 @@ export class CallExpression extends Expression { ) { super('CallExpression') } + + public description( + result: Partial<'value' | EvaluationResultCallExpression> + ): string { + const argsDescription = '()' + return `${ + this.callee.name.lexeme + }${argsDescription} (which returned ${quoteLiteral( + result.value + )})` + } } export class ArrayExpression extends Expression { diff --git a/app/javascript/interpreter/frames.ts b/app/javascript/interpreter/frames.ts index 7f4ed4143c..afff4f24ef 100644 --- a/app/javascript/interpreter/frames.ts +++ b/app/javascript/interpreter/frames.ts @@ -5,10 +5,12 @@ export type FrameExecutionStatus = 'SUCCESS' | 'ERROR' import type { EvaluationResult, EvaluationResultChangeVariableStatement, + EvaluationResultIfStatement, } from './evaluation-result' import type { ExternalFunction } from './executor' import { BinaryExpression, + CallExpression, Expression, GroupingExpression, LiteralExpression, @@ -49,33 +51,41 @@ export function describeFrame( frame: Frame, externalFunctions: ExternalFunction[] ): string { - // These need to come from the exercise. - const functionDescriptions: Record = externalFunctions.reduce( - (acc: Record, fn: ExternalFunction) => { - acc[fn.name] = fn.description - return acc - }, - {} - ) + try { + console.log(process.env.NODE_ENV) + } catch {} + try { + // These need to come from the exercise. + const functionDescriptions: Record = + externalFunctions.reduce( + (acc: Record, fn: ExternalFunction) => { + acc[fn.name] = fn.description + return acc + }, + {} + ) - if (!isFrameWithResult(frame)) { - return '

There is no information available for this line.

' - } - switch (frame.result.type) { - case 'SetVariableStatement': - return describeSetVariableStatement(frame) - case 'ForeachStatement': - return describeForeachStatement(frame) - case 'ChangeVariableStatement': - return describeChangeVariableStatement(frame) - case 'IfStatement': - return describeIfStatement(frame) - case 'ReturnStatement': - return describeReturnStatement(frame) - case 'CallExpression': - return describeCallExpression(frame, functionDescriptions) - default: - return `

There is no information available for this line.

` + if (!isFrameWithResult(frame)) { + return '

There is no information available for this line.

' + } + switch (frame.result.type) { + case 'SetVariableStatement': + return describeSetVariableStatement(frame) + case 'ForeachStatement': + return describeForeachStatement(frame) + case 'ChangeVariableStatement': + return describeChangeVariableStatement(frame) + case 'IfStatement': + return describeIfStatement(frame) + case 'ReturnStatement': + return describeReturnStatement(frame) + case 'CallExpression': + return describeCallExpression(frame, functionDescriptions) + default: + return `

There is no information available for this line. Show us your code in Discord and we'll improve this!

` + } + } catch (e) { + return `

There is no information available for this line. Show us your code in Discord and we'll improve this!

` } } @@ -109,36 +119,35 @@ function describeChangeVariableStatement(frame: FrameWithResult): string { ) } -function describeForeachStatement(frame: FrameWithResult) { - let output = `

This looped through ${frame.result.iterable.name} array. Each time this line of code is run, it selects the next item from the array and assigns to the ${frame.result.elementName} variable.

` +function describeForeachStatement(result: EvaluationResult) { + let output = `

This looped through ${result.iterable.name} array. Each time this line of code is run, it selects the next item from the array and assigns to the ${frame.result.elementName} variable.

` - if (frame.result.value) { - output += `

This iteration set ${frame.result.elementName} to:

` - output += `
${JSON.stringify(
-      frame.result.value,
-      null,
-      2
-    )}
` + if (result.value) { + output += `

This iteration set ${result.elementName} to:

` + output += `
${JSON.stringify(result.value, null, 2)}
` } return output } -function describeExpression(expression: Expression) { +function describeExpression(expression: Expression, result?: EvaluationResult) { if (expression instanceof VariableExpression) { return expression.description() } if (expression instanceof LiteralExpression) { return expression.description() } + if (expression instanceof CallExpression) { + return expression.description(result) + } if (expression instanceof GroupingExpression) { - return describeGroupingExpression(expression) + return describeGroupingExpression(expression, result) } if (expression instanceof BinaryExpression) { - return describeBinaryExpression(expression) + return describeBinaryExpression(expression, result) } if (expression instanceof LogicalExpression) { - return describeLogicalExpression(expression) + return describeLogicalExpression(expression, result) } return '' } @@ -164,23 +173,26 @@ function describeOperator(operator: string): string { return '' } -function describeBinaryExpression(expression: BinaryExpression): string { - if (expression instanceof BinaryExpression) { - const left = describeExpression(expression.left) - const right = describeExpression(expression.right) - const operator = describeOperator(expression.operator.type) - if (isEqualityOperator(expression.operator.type)) { - return `${left} was ${operator} ${right}` - } else { - return `${left} ${operator} ${right}` - } +function describeBinaryExpression( + expression: BinaryExpression, + result?: EvaluationResult +): string { + const left = describeExpression(expression.left, result?.left) + const right = describeExpression(expression.right, result?.right) + const operator = describeOperator(expression.operator.type) + if (isEqualityOperator(expression.operator.type)) { + return `${left} was ${operator} ${right}` + } else { + return `${left} ${operator} ${right}` } - return '' } -function describeLogicalExpression(expression: LogicalExpression): string { - const left = describeExpression(expression.left) - const right = describeExpression(expression.right) +function describeLogicalExpression( + expression: LogicalExpression, + result?: EvaluationResult +): string { + const left = describeExpression(expression.left, result?.left) + const right = describeExpression(expression.right, result?.right) if (expression.operator.type == 'AND') { return `both of these were true:

  • ${left}
  • ${right}

` @@ -189,21 +201,30 @@ function describeLogicalExpression(expression: LogicalExpression): string { } } -function describeGroupingExpression(expression: GroupingExpression): string { - return `${describeExpression(expression.inner)}` +function describeGroupingExpression( + expression: GroupingExpression, + result: EvaluationResult +): string { + return `${describeExpression(expression.inner, result)}` } -function describeCondition(expression: Expression): string { - return describeExpression(expression) +function describeCondition( + expression: Expression, + result: EvaluationResultIfStatement +): string { + return describeExpression(expression, result.condition) } function describeIfStatement(frame: FrameWithResult) { const ifStatement = frame.context as IfStatement - const conditionDescription = describeCondition(ifStatement.condition) + const conditionDescription = describeCondition( + ifStatement.condition, + frame.result + ) let output = ` -

This checked whether ${conditionDescription}

-

The result was ${frame.result.value}.

- ` +

This checked whether ${conditionDescription}

+

The result was ${frame.result.value}.

+ `.trim() return output } function describeReturnStatement(frame: FrameWithResult) { diff --git a/test/javascript/interpreter/expression_descriptions.test.ts b/test/javascript/interpreter/expression_descriptions.test.ts index afee3f8755..ef11046d2e 100644 --- a/test/javascript/interpreter/expression_descriptions.test.ts +++ b/test/javascript/interpreter/expression_descriptions.test.ts @@ -1,12 +1,32 @@ import { interpret } from '@/interpreter/interpreter' import type { ExecutionContext } from '@/interpreter/executor' -import { LiteralExpression, VariableExpression } from '@/interpreter/expression' +import { + CallExpression, + LiteralExpression, + VariableExpression, +} from '@/interpreter/expression' import { Location } from '@/interpreter/location' import { Span } from '@/interpreter/location' import { type Token, TokenType } from '@/interpreter/token' const location = new Location(0, new Span(0, 0), new Span(0, 0)) +const functionVariableToken: Token = { + lexeme: 'getName', + type: 'FUNCTION', + literal: 'name', + location: location, +} +const parenToken: Token = { + lexeme: '(', + type: 'LEFT_PAREN', + literal: '(', + location: location, +} +const functionVariableExpr = new VariableExpression( + functionVariableToken, + location +) describe('LiteralExpression', () => { describe('description', () => { test('number', () => { @@ -42,3 +62,23 @@ describe('VariableExpression', () => { }) }) }) + +describe('CallExpression', () => { + describe('description', () => { + test('no args', () => { + const expr = new CallExpression( + functionVariableExpr, + parenToken, + [], + location + ) + const result = { + value: 'Jeremy', + } + const actual = expr.description(result) + expect(actual).toBe( + 'getName() (which returned "Jeremy")' + ) + }) + }) +}) diff --git a/test/javascript/interpreter/statement_descriptions.test.ts b/test/javascript/interpreter/statement_descriptions.test.ts index 2220913069..4366e11df4 100644 --- a/test/javascript/interpreter/statement_descriptions.test.ts +++ b/test/javascript/interpreter/statement_descriptions.test.ts @@ -8,6 +8,27 @@ import { SetVariableStatement } from '@/interpreter/statement' import { describeFrame } from '@/interpreter/frames' const location = new Location(0, new Span(0, 0), new Span(0, 0)) +const getNameFunction = { + name: 'get_name', + func: (_interpreter: any) => { + return 'Jeremy' + }, + description: '', +} +const getTrueFunction = { + name: 'get_true', + func: (_interpreter: any) => { + return true + }, + description: '', +} +const getFalseFunction = { + name: 'get_false', + func: (_interpreter: any) => { + return false + }, + description: '', +} describe('SetVariableStatement', () => { describe('description', () => { @@ -18,6 +39,15 @@ describe('SetVariableStatement', () => { '

This created a new variable called my_name and sets its value to "Jeremy".

' ) }) + test('function', () => { + const { frames } = interpret('set my_name to get_name()', { + externalFunctions: [getNameFunction], + }) + const actual = describeFrame(frames[0], []) + expect(actual).toBe( + '

This created a new variable called my_name and sets its value to "Jeremy".

' + ) + }) }) }) @@ -35,3 +65,92 @@ describe('ChangeVariableStatement', () => { }) }) }) + +describe('IfStatement', () => { + describe('description', () => { + test('booleans', () => { + const { error, frames } = interpret(` + if true is true do + set name to "Jeremy" + end + `) + console.log(error) + const actual = describeFrame(frames[0], []) + expect(actual).toBe( + `

This checked whether true was equal to true

\n

The result was true.

` + ) + }) + test('booleans and', () => { + const { error, frames } = interpret(` + if true is true and true is true do + set name to "Jeremy" + end + `) + console.log(error) + const actual = describeFrame(frames[0], []) + expect(actual).toBe( + `

This checked whether both of these were true:

  • true was equal to true
  • true was equal to true

\n

The result was true.

` + ) + }) + test('function vs boolean', () => { + const { error, frames } = interpret( + ` + if get_true() is true do + set name to "Jeremy" + end + `, + { externalFunctions: [getTrueFunction] } + ) + console.log(frames[0]) + const actual = describeFrame(frames[0], []) + expect(actual).toBe( + `

This checked whether get_true() (which returned true) was equal to true

\n

The result was true.

` + ) + }) + test('boolean vs function', () => { + const { error, frames } = interpret( + ` + if true is get_true() do + set name to "Jeremy" + end + `, + { externalFunctions: [getTrueFunction] } + ) + console.log(frames[0]) + const actual = describeFrame(frames[0], []) + expect(actual).toBe( + `

This checked whether true was equal to get_true() (which returned true)

\n

The result was true.

` + ) + }) + test('function vs function', () => { + const { error, frames } = interpret( + ` + if get_true() is get_false() do + set name to "Jeremy" + end + `, + { externalFunctions: [getTrueFunction, getFalseFunction] } + ) + console.log(frames[0]) + const actual = describeFrame(frames[0], []) + expect(actual).toBe( + `

This checked whether get_true() (which returned true) was equal to get_false() (which returned false)

\n

The result was false.

` + ) + }) + test('function vs function with and', () => { + const { error, frames } = interpret( + ` + if get_true() is get_true() and get_false() is get_false() do + set name to "Jeremy" + end + `, + { externalFunctions: [getTrueFunction, getFalseFunction] } + ) + console.log(frames[0]) + const actual = describeFrame(frames[0], []) + expect(actual).toBe( + `

This checked whether both of these were true:

  • get_true() (which returned true) was equal to get_true() (which returned true)
  • get_false() (which returned false) was equal to get_false() (which returned false)

\n

The result was true.

` + ) + }) + }) +})