diff --git a/app/javascript/interpreter/error.ts b/app/javascript/interpreter/error.ts index 44598400d6..c43723dd4a 100644 --- a/app/javascript/interpreter/error.ts +++ b/app/javascript/interpreter/error.ts @@ -108,6 +108,9 @@ export type RuntimeErrorType = | 'UnexpectedEqualsForEquality' | 'VariableAlreadyDeclared' | 'VariableNotDeclared' + | 'UnexpectedUncalledFunction' + | 'FunctionAlreadyDeclared' + | 'UnexpectedChangeOfFunction' export type StaticErrorType = | DisabledLanguageFeatureErrorType diff --git a/app/javascript/interpreter/executor.ts b/app/javascript/interpreter/executor.ts index ae5e95065e..685e0f79aa 100644 --- a/app/javascript/interpreter/executor.ts +++ b/app/javascript/interpreter/executor.ts @@ -20,6 +20,7 @@ import { UnaryExpression, UpdateExpression, VariableExpression, + ExpressionWithValue, } from './expression' import { Location, Span } from './location' import { @@ -215,7 +216,12 @@ export class Executor { public visitSetVariableStatement(statement: SetVariableStatement): void { this.executeFrame(statement, () => { - if (this.environment.inScope(statement.name.lexeme)) { + if (this.environment.inScope(statement.name)) { + if (isCallable(this.environment.get(statement.name))) { + this.error('FunctionAlreadyDeclared', statement.name.location, { + name: statement.name.lexeme, + }) + } this.error('VariableAlreadyDeclared', statement.location, { name: statement.name.lexeme, }) @@ -252,6 +258,12 @@ export class Executor { }) } + if (isCallable(this.environment.get(statement.name))) { + this.error('UnexpectedChangeOfFunction', statement.name.location, { + name: statement.name.lexeme, + }) + } + const value = this.evaluate(statement.value) this.updateVariable(statement.name, value.value, statement) @@ -621,55 +633,85 @@ export class Executor { } public visitBinaryExpression(expression: BinaryExpression): EvaluationResult { - const left = this.evaluate(expression.left) - const right = this.evaluate(expression.right) + const leftResult = this.evaluate(expression.left) + const rightResult = this.evaluate(expression.right) const result: EvaluationResult = { type: 'BinaryExpression', value: undefined, operator: expression.operator.type, - left, - right, + left: leftResult, + right: rightResult, } switch (expression.operator.type) { case 'INEQUALITY': // TODO: throw error when types are not the same? - return { ...result, value: left.value !== right.value } + return { ...result, value: leftResult.value !== rightResult.value } case 'EQUALITY': // TODO: throw error when types are not the same? return { ...result, - value: left.value === right.value, + value: leftResult.value === rightResult.value, } case 'GREATER': - this.verifyNumberOperands(expression.operator, left.value, right.value) + this.verifyNumberOperands( + expression.operator, + expression.left, + expression.right, + leftResult.value, + rightResult.value + ) return { ...result, - value: left.value > right.value, + value: leftResult.value > rightResult.value, } case 'GREATER_EQUAL': - this.verifyNumberOperands(expression.operator, left.value, right.value) + this.verifyNumberOperands( + expression.operator, + expression.left, + expression.right, + leftResult.value, + rightResult.value + ) return { ...result, - value: left.value >= right.value, + value: leftResult.value >= rightResult.value, } case 'LESS': - this.verifyNumberOperands(expression.operator, left.value, right.value) + this.verifyNumberOperands( + expression.operator, + expression.left, + expression.right, + leftResult.value, + rightResult.value + ) return { ...result, - value: left.value < right.value, + value: leftResult.value < rightResult.value, } case 'LESS_EQUAL': - this.verifyNumberOperands(expression.operator, left.value, right.value) + this.verifyNumberOperands( + expression.operator, + expression.left, + expression.right, + leftResult.value, + rightResult.value + ) return { ...result, - value: left.value <= right.value, + value: leftResult.value <= rightResult.value, } case 'MINUS': - this.verifyNumberOperands(expression.operator, left.value, right.value) + this.verifyNumberOperands( + expression.operator, + expression.left, + expression.right, + leftResult.value, + rightResult.value + ) - const minusValue = left.value - right.value + const minusValue = leftResult.value - rightResult.value const minusValue2DP = Math.round(minusValue * 100) / 100 return { @@ -678,8 +720,24 @@ export class Executor { } //> binary-plus case 'PLUS': - if (isNumber(left.value) && isNumber(right.value)) { - const plusValue = left.value + right.value + this.verifyNumberOperands( + expression.operator, + expression.left, + expression.right, + leftResult.value, + rightResult.value + ) + + const plusValue = leftResult.value + rightResult.value + const plusValue2DP = Math.round(plusValue * 100) / 100 + + return { + ...result, + value: plusValue2DP, + } + + if (isNumber(leftResult.value) && isNumber(rightResult.value)) { + const plusValue = leftResult.value + rightResult.value const plusValue2DP = Math.round(plusValue * 100) / 100 return { @@ -687,7 +745,7 @@ export class Executor { value: plusValue2DP, } } - if (isString(left.value) && isString(right.value)) + /*if (isString(left.value) && isString(right.value)) return { ...result, value: left.value + right.value, @@ -700,31 +758,49 @@ export class Executor { left, right, } - ) + )*/ case 'SLASH': - this.verifyNumberOperands(expression.operator, left.value, right.value) - const slashValue = left.value / right.value + this.verifyNumberOperands( + expression.operator, + expression.left, + expression.right, + leftResult.value, + rightResult.value + ) + const slashValue = leftResult.value / rightResult.value const slashValue2DP = Math.round(slashValue * 100) / 100 return { ...result, value: slashValue2DP, } case 'STAR': - this.verifyNumberOperands(expression.operator, left.value, right.value) + this.verifyNumberOperands( + expression.operator, + expression.left, + expression.right, + leftResult.value, + rightResult.value + ) - const starValue = left.value * right.value + const starValue = leftResult.value * rightResult.value const starValue2DP = Math.round(starValue * 100) / 100 return { ...result, value: starValue2DP, } case 'PERCENT': - this.verifyNumberOperands(expression.operator, left.value, right.value) + this.verifyNumberOperands( + expression.operator, + expression.left, + expression.right, + leftResult.value, + rightResult.value + ) return { ...result, - value: left.value % right.value, + value: leftResult.value % rightResult.value, } case 'EQUAL': this.error('UnexpectedEqualsForEquality', expression.location, { @@ -919,10 +995,48 @@ export class Executor { this.error('OperandMustBeNumber', operator.location, { operand }) } - private verifyNumberOperands(operator: Token, left: any, right: any): void { - if (isNumber(left) && isNumber(right)) return + private verifyNumberOperands( + operator: Token, + leftExpr: Expression, + rightExpr: Expression, + leftValue: EvaluationResult, + rightValue: EvaluationResult + ): void { + const leftIsNumber = isNumber(leftValue) + const rightIsNumber = isNumber(rightValue) + if (leftIsNumber && rightIsNumber) { + return + } - this.error('OperandsMustBeNumbers', operator.location, { left, right }) + let value + let eroneousExpr + let location + if (leftIsNumber) { + value = rightValue + eroneousExpr = rightExpr + location = Location.between(operator, rightExpr) + } else { + value = leftValue + eroneousExpr = leftExpr + location = Location.between(leftExpr, operator) + } + + if (isCallable(value)) { + this.error('UnexpectedUncalledFunction', eroneousExpr.location, { + name: eroneousExpr.name, + }) + } + + // Quote strings + if (typeof value == 'string') { + //value = `"${value}""` + } + + this.error('OperandsMustBeNumbers', location, { + operator: operator.lexeme, + side: leftIsNumber ? 'right' : 'left', + value: `\`${value}\``, + }) } private verifyBooleanOperand(operand: any, location: Location): void { diff --git a/app/javascript/interpreter/locales/en/translation.json b/app/javascript/interpreter/locales/en/translation.json index d231ff5e28..195d334118 100644 --- a/app/javascript/interpreter/locales/en/translation.json +++ b/app/javascript/interpreter/locales/en/translation.json @@ -82,13 +82,16 @@ "NonCallableTarget": "Can only call functions.", "OperandMustBeBoolean": "Operand must be a boolean.", "OperandMustBeNumber": "Operand must be a number.", - "OperandsMustBeNumber": "Operands must be numbers.", + "OperandsMustBeNumbers": "Both sides of the `{{operator}}` must be numbers. The {{side}} side is not a number. It is {{value}}.", "OperandsMustBeTwoNumbersOrTwoStrings": "Operands must be two numbers or two strings.", "RepeatCountMustBeGreaterThanZero": "You must repeat things at least once.", "RepeatCountMustBeLessThanOneThousand": "The most times you can repeat things is 1000.", "RepeatCountMustBeNumber": "repeat can only take a number as its argument.", "VariableAlreadyDeclared": "A variable with this name has already been created. Did you mean to use the change keyword?", - "VariableNotDeclared": "Did you forget to use the `set` keyword to create a variable called `{{name}}` before this line of code?" + "VariableNotDeclared": "Did you forget to use the `set` keyword to create a variable called `{{name}}` before this line of code?", + "UnexpectedUncalledFunction": "You used a function as if it's a variable. Did you mean to use it by adding `()` at the end?", + "FunctionAlreadyDeclared": "There is already a function called `{{name}}`. Please choose a different name for this variable.", + "UnexpectedChangeOfFunction": "You are trying to change `{{name}}` but it is a function, not a variable.\n\nIt is not a box that you can change the contents of. It is machine for you to use." }, "disabledLanguageFeature": { "ExcludeListViolation": "Jiki doesn't know how to use `{{lexeme}}` in this exercise.", diff --git a/app/javascript/interpreter/locales/system/translation.json b/app/javascript/interpreter/locales/system/translation.json index bcfd56862f..591258a2b1 100644 --- a/app/javascript/interpreter/locales/system/translation.json +++ b/app/javascript/interpreter/locales/system/translation.json @@ -81,12 +81,15 @@ "NonCallableTarget": "NonCallableTarget", "OperandMustBeBoolean": "OperandMustBeBoolean", "OperandMustBeNumber": "OperandMustBeNumber", - "OperandsMustBeNumber": "OperandsMustBeNumber", + "OperandsMustBeNumbers": "OperandsMustBeNumbers: operator: {{operator}}, side: {{side}}, value: {{value}}", "OperandsMustBeTwoNumbersOrTwoStrings": "OperandsMustBeTwoNumbersOrTwoStrings", "RepeatCountMustBeGreaterThanZero": "RepeatCountMustBeGreaterThanZero", "RepeatCountMustBeNumber": "RepeatCountMustBeNumber", "TooManyArguments": "TooManyArguments: arity: {{arity}}, numberOfArgs: {{numberOfArgs}}", - "TooFewArguments": "TooFewArguments: arity: {{arity}}, numberOfArgs: {{numberOfArgs}}" + "TooFewArguments": "TooFewArguments: arity: {{arity}}, numberOfArgs: {{numberOfArgs}}", + "UnexpectedUncalledFunction": "UnexpectedUncalledFunction", + "FunctionAlreadyDeclared": "FunctionAlreadyDeclared: name: {{name}}", + "UnexpectedChangeOfFunction": "UnexpectedChangeOfFunction: name: {{name}}" }, "disabledLanguageFeature": { "ExcludeListViolation": "ExcludeListViolation: tokenType: {{tokenType}}", diff --git a/bootcamp_content/projects/two-fer/config.json b/bootcamp_content/projects/two-fer/config.json deleted file mode 100644 index 0e383e2435..0000000000 --- a/bootcamp_content/projects/two-fer/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "slug": "two-fer", - "title": "TwoFer", - "description": "Cookies, strings, conditionals", - "exercises": ["basic"] -} diff --git a/bootcamp_content/projects/two-fer/exercises/basic/config.json b/bootcamp_content/projects/two-fer/exercises/basic/config.json deleted file mode 100644 index aed6b3a61c..0000000000 --- a/bootcamp_content/projects/two-fer/exercises/basic/config.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "title": "Empty Strings", - "description": "Return what you say when you hand the cookie other, using the person's name if you know it.", - "concepts": ["conditionals"], - "level": 3, - "idx": 1, - "tests_type": "io", - "readonly_ranges": [ - { "from": 1, "to": 1 }, - { "from": 3, "to": 3 } - ], - "tasks": [ - { - "name": "Get things working with a single string", - "tests": [ - { - "slug": "no_name", - "name": "no name given", - "function": "two_fer", - "params": [""], - "expected": "One for you, one for me." - } - ] - }, - { - "name": "Get things working with a name", - "tests": [ - { - "type": "io", - "slug": "alice", - "name": "A name is given", - "function": "two_fer", - "params": ["Alice"], - "expected": "One for Alice, one for me." - }, - { - "type": "io", - "slug": "bob", - "name": "Another name is given", - "function": "two_fer", - "params": ["Bob"], - "expected": "One for Bob, one for me." - } - ] - } - ] -} diff --git a/bootcamp_content/projects/two-fer/exercises/basic/example.jiki b/bootcamp_content/projects/two-fer/exercises/basic/example.jiki deleted file mode 100644 index 802eb8f278..0000000000 --- a/bootcamp_content/projects/two-fer/exercises/basic/example.jiki +++ /dev/null @@ -1,7 +0,0 @@ -function two_fer with name do - if name is "" do - return "One for you, one for me." - else do - return "One for " + name + ", one for me." - end -end diff --git a/bootcamp_content/projects/two-fer/exercises/basic/introduction.md b/bootcamp_content/projects/two-fer/exercises/basic/introduction.md deleted file mode 100644 index c8048e8dea..0000000000 --- a/bootcamp_content/projects/two-fer/exercises/basic/introduction.md +++ /dev/null @@ -1,15 +0,0 @@ -# TwoFer Part 1 - -Two Fer is a classic Exercism exercise. -Through it, we'll explore a few ideas around using _Strings_ and _Conditionals_. - -In some English accents, when you say "two for" quickly, it sounds like "two fer". Two-for-one is a way of saying that if you buy one, you also get one for free. So the phrase "two-fer" often implies a two-for-one offer. - -Imagine a bakery that has a holiday offer where you can buy two cookies for the price of one ("two-fer one!"). You take the offer and (very generously) decide to give the extra cookie to someone else in the queue. - -As you give them the cookie, you one of two things. - -- If you know their name, you say: "One for , one for me." -- If you don't know their name, you say: "One for you, one for me." - -For example, you might say "One for Jeremy, one for me" if you know Jeremy's name. diff --git a/bootcamp_content/projects/two-fer/exercises/basic/stub.jiki b/bootcamp_content/projects/two-fer/exercises/basic/stub.jiki deleted file mode 100644 index e871b02348..0000000000 --- a/bootcamp_content/projects/two-fer/exercises/basic/stub.jiki +++ /dev/null @@ -1,3 +0,0 @@ -function two_fer with name do - -end diff --git a/bootcamp_content/projects/two-fer/exercises/basic/task-1.md b/bootcamp_content/projects/two-fer/exercises/basic/task-1.md deleted file mode 100644 index 1b03934503..0000000000 --- a/bootcamp_content/projects/two-fer/exercises/basic/task-1.md +++ /dev/null @@ -1,5 +0,0 @@ -We've given you a function skeleton that takes one input - `name`. - -It will either be an empty string (`""`) or it will be someone's name (`"Jeremy"`). - -Let's start off by just considering that empty version and always returning our default version `"One for me, one for you."`. diff --git a/bootcamp_content/projects/two-fer/exercises/basic/task-2.md b/bootcamp_content/projects/two-fer/exercises/basic/task-2.md deleted file mode 100644 index 2a597efd6a..0000000000 --- a/bootcamp_content/projects/two-fer/exercises/basic/task-2.md +++ /dev/null @@ -1,7 +0,0 @@ -Nice work! - -Now we need to handle the situation where we **do** know the person's name. - -Sometime's the name will be empty (`""`) in which case we want to continue returning `"One for you, one for me."`, but other times `name` will contain a name, in which case we want to include it in the return value (e.g. `"One for Jeremy, one for me."`). - -Remember, you can join multiple strings together using the `join_strings(...)` function. diff --git a/bootcamp_content/projects/two-fer/introduction.md b/bootcamp_content/projects/two-fer/introduction.md deleted file mode 100644 index f3cf6682e4..0000000000 --- a/bootcamp_content/projects/two-fer/introduction.md +++ /dev/null @@ -1,25 +0,0 @@ -# Two Fer - -## Overview - -Two Fer is a classic Exercism exercise. -Through it, we'll explore a few ideas around using _Strings_ and _Conditionals_. - -## Introduction - -In some English accents, when you say "two for" quickly, it sounds like "two fer". Two-for-one is a way of saying that if you buy one, you also get one for free. So the phrase "two-fer" often implies a two-for-one offer. - -Imagine a bakery that has a holiday offer where you can buy two cookies for the price of one ("two-fer one!"). You take the offer and (very generously) decide to give the extra cookie to someone else in the queue. - -As you give them the cookie, if you know their name (e.g. they're called "Jeremy"), you say: - -```text -"One for Jeremy, one for me." -``` - -If you don't know their name (even more generous of you!), you say: -"One for you, one for me." - -``` - -``` diff --git a/bootcamp_content/projects/wordle/config.json b/bootcamp_content/projects/wordle/config.json deleted file mode 100644 index 1bbcca61f0..0000000000 --- a/bootcamp_content/projects/wordle/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "slug": "wordle", - "title": "Wordle", - "description": "Create the modern Wordle game", - "exercises": ["process-guess"] -} diff --git a/bootcamp_content/projects/wordle/exercises/process-guess/config.json b/bootcamp_content/projects/wordle/exercises/process-guess/config.json deleted file mode 100644 index b33fe3138d..0000000000 --- a/bootcamp_content/projects/wordle/exercises/process-guess/config.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "title": "Process a guess", - "description": "Turn a guess into a valid response", - "project_type": "wordle", - "available_functions": [], - "concepts": ["conditionals", "arrays"], - "level": 3, - "idx": 1, - "tests_type": "state", - "tasks": [ - { - "name": "Deal with a correct guess", - "tests": [ - { - "slug": "all-correct", - "name": "Deal with a fully correct guess", - "function": "process_guess", - "checks": [ - { - "name": "getGuessState1()", - "value": ["present", "absent", "absent", "absent", "correct"], - "error_html": "We expected the first letter to be present and the last one to be correct" - } - ] - } - ] - } - ] -} diff --git a/bootcamp_content/projects/wordle/exercises/process-guess/example.jiki b/bootcamp_content/projects/wordle/exercises/process-guess/example.jiki deleted file mode 100644 index 7e4fc86905..0000000000 --- a/bootcamp_content/projects/wordle/exercises/process-guess/example.jiki +++ /dev/null @@ -1,6 +0,0 @@ -move() -move() -turn_left() -move() -move() -turn_right() \ No newline at end of file diff --git a/bootcamp_content/projects/wordle/exercises/process-guess/introduction.md b/bootcamp_content/projects/wordle/exercises/process-guess/introduction.md deleted file mode 100644 index bf45496945..0000000000 --- a/bootcamp_content/projects/wordle/exercises/process-guess/introduction.md +++ /dev/null @@ -1,11 +0,0 @@ -# Solve the Maze - -Your task is to solve the following maze. - -You have three functions you can use: - -- `move()` which moves the character one step forward -- `turn_left()` turns the character left (relative to the direction they're currently facing) -- `turn_right()` turns the character right (relative to the direction they're currently facing) - -Remember to use one function per line. diff --git a/bootcamp_content/projects/wordle/exercises/process-guess/stub.jk b/bootcamp_content/projects/wordle/exercises/process-guess/stub.jk deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bootcamp_content/projects/wordle/exercises/process-guess/task-1.md b/bootcamp_content/projects/wordle/exercises/process-guess/task-1.md deleted file mode 100644 index b470c01201..0000000000 --- a/bootcamp_content/projects/wordle/exercises/process-guess/task-1.md +++ /dev/null @@ -1,8 +0,0 @@ -# Task 1 - -We've started by adding a single `move()` for you, which will move the character one step forward. - -Use the "Run code" button to see how close you're getting. - -It's good practice to get into the habit of running your code reguarly. -In this exercise, we'd recommend running it after adding each new instruction, although you might like to try and challenge yourself to solve it all in one go instead! diff --git a/bootcamp_content/projects/wordle/introduction.md b/bootcamp_content/projects/wordle/introduction.md deleted file mode 100644 index 2d376d908f..0000000000 --- a/bootcamp_content/projects/wordle/introduction.md +++ /dev/null @@ -1,7 +0,0 @@ -# Maze - -## Overview - -This project contains the first exercise you ever do, but continues all the way through into Part 2. - -Your job is to solve, and then later create a maze that your character can explore through. diff --git a/test/javascript/interpreter/interpreter.test.ts b/test/javascript/interpreter/interpreter.test.ts index b0d8d5b9b4..b6e13d0062 100644 --- a/test/javascript/interpreter/interpreter.test.ts +++ b/test/javascript/interpreter/interpreter.test.ts @@ -153,7 +153,7 @@ describe('statements', () => { }) describe('strings', () => { - test('plus', () => { + test.skip('plus', () => { const { frames } = interpret('set x to "sw" + "eet" ') expect(frames).toBeArrayOfSize(1) expect(frames[0].status).toBe('SUCCESS') @@ -268,7 +268,7 @@ describe('statements', () => { expect(frames[3].variables).toMatchObject({ pos: 25 }) }) }) - describe('function', () => { + describe('creating function', () => { describe('without parameters', () => { test('define', () => { const { frames } = interpret(` @@ -787,7 +787,7 @@ describe('evaluateFunction', () => { expect(frames).toBeArrayOfSize(11) }) - test('twoFer', () => { + test.skip('twoFer', () => { const code = ` function twoFer with name do if(name is "") do diff --git a/test/javascript/interpreter/runtimeErrors.test.ts b/test/javascript/interpreter/runtimeErrors.test.ts new file mode 100644 index 0000000000..eb4ed9e46c --- /dev/null +++ b/test/javascript/interpreter/runtimeErrors.test.ts @@ -0,0 +1,114 @@ +import { interpret } from '@/interpreter/interpreter' +import { Location, Span } from '@/interpreter/location' +import { changeLanguage } from '@/interpreter/translator' +import { context } from 'msw' + +beforeAll(() => { + changeLanguage('system') +}) + +afterAll(() => { + changeLanguage('en') +}) + +const location = new Location(0, new Span(0, 0), new Span(0, 0)) +const getNameFunction = { + name: 'get_name', + func: (_interpreter: any) => { + return 'Jeremy' + }, + description: '', +} + +function expectFrameToBeError(frame, code, type) { + expect(frame.code).toBe(code) + expect(frame.status).toBe('ERROR') + expect(frame.error).not.toBeNull() + expect(frame.error!.category).toBe('RuntimeError') + expect(frame.error!.type).toBe(type) +} + +describe('Runtime errors', () => { + describe('OperandsMustBeNumbers', () => { + test('1 - "a"', () => { + const code = '1 - "a"' + const { frames } = interpret(code) + expectFrameToBeError(frames[0], code, 'OperandsMustBeNumbers') + expect(frames[0].error!.message).toBe( + 'OperandsMustBeNumbers: operator: -, side: right, value: `a`' + ) + }) + test('1 / true', () => { + const code = '1 / true' + const { frames } = interpret(code) + expectFrameToBeError(frames[0], code, 'OperandsMustBeNumbers') + expect(frames[0].error!.message).toBe( + 'OperandsMustBeNumbers: operator: /, side: right, value: `true`' + ) + }) + test('false - 1', () => { + const code = 'false - 1' + const { frames } = interpret(code) + expectFrameToBeError(frames[0], code, 'OperandsMustBeNumbers') + expect(frames[0].error!.message).toBe( + 'OperandsMustBeNumbers: operator: -, side: left, value: `false`' + ) + }) + test('1 * false', () => { + const code = '1 * false' + const { frames } = interpret(code) + expectFrameToBeError(frames[0], code, 'OperandsMustBeNumbers') + expect(frames[0].error!.message).toBe( + 'OperandsMustBeNumbers: operator: *, side: right, value: `false`' + ) + }) + test('1 * getName()', () => { + const code = '1 * get_name()' + const context = { externalFunctions: [getNameFunction] } + const { frames } = interpret(code, context) + expectFrameToBeError(frames[0], code, 'OperandsMustBeNumbers') + expect(frames[0].error!.message).toBe( + 'OperandsMustBeNumbers: operator: *, side: right, value: `Jeremy`' + ) + }) + }) + + describe('UnexpectedUncalledFunction', () => { + test('in a equation with a +', () => { + const code = 'get_name + 1' + const context = { externalFunctions: [getNameFunction] } + const { frames } = interpret(code, context) + expectFrameToBeError(frames[0], code, 'UnexpectedUncalledFunction') + expect(frames[0].error!.message).toBe('UnexpectedUncalledFunction') + }) + test('in a equation with a -', () => { + const code = 'get_name - 1' + const context = { externalFunctions: [getNameFunction] } + const { frames } = interpret(code, context) + expectFrameToBeError(frames[0], code, 'UnexpectedUncalledFunction') + expect(frames[0].error!.message).toBe('UnexpectedUncalledFunction') + }) + }) + describe('FunctionAlreadyDeclared', () => { + test('basic', () => { + const code = 'set get_name to 5' + const context = { externalFunctions: [getNameFunction] } + const { frames } = interpret(code, context) + expectFrameToBeError(frames[0], code, 'FunctionAlreadyDeclared') + expect(frames[0].error!.message).toBe( + 'FunctionAlreadyDeclared: name: get_name' + ) + }) + }) + describe('UnexpectedChangeOfFunction', () => { + test('basic', () => { + const code = 'change get_name to 5' + const context = { externalFunctions: [getNameFunction] } + const { frames } = interpret(code, context) + expectFrameToBeError(frames[0], code, 'UnexpectedChangeOfFunction') + expect(frames[0].error!.message).toBe( + 'UnexpectedChangeOfFunction: name: get_name' + ) + }) + }) +})