diff --git a/examples/char.fr b/examples/char.fr index aa9f65c..bcd07b8 100644 --- a/examples/char.fr +++ b/examples/char.fr @@ -1,9 +1,21 @@ -programme Caractères +programme ExempleCaractères début - avec rl, mon_char: caractère + avec mon_char: caractère + rl: chaîne - rl <- '\n' + # `rl` est juste un retour à la ligne. + # Seulement pour montrer que ça fonctionne. + rl <- "\n" mon_char <- 'a' - afficher "Mon caractère = " + mon_char + rl - afficher "Mon caractère+1 = " + (mon_char+1) + rl -fin Caractères + + # On peut afficher les caractères + # et effectuer des concatenations "chaîne + car". + afficher "mon_char = " + mon_char + rl + + # Les caractères sont considérés comme des nombres, + # donc toutes les opérations sont possible sur eux. + afficher "mon_char + 1 = " + (mon_char + 1) + rl + + # On peut ainsi additionner deux caractères, par exemple. + afficher "'z' - ' ' = " + ('z' - ' ') + rl +fin ExempleCaractères diff --git "a/examples/proc\303\251dure.fr" "b/examples/proc\303\251dure.fr" index 9384638..d8a73fa 100644 --- "a/examples/proc\303\251dure.fr" +++ "b/examples/proc\303\251dure.fr" @@ -8,5 +8,5 @@ début avec mon_nombre : entier mon_nombre <- 10 - hello(b) + hello(mon_nombre) fin World diff --git a/src/cli.ts b/src/cli.ts index 32e0a21..3bf4c0e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -41,7 +41,15 @@ try { // We interpret the code. const interpreter = new Interpreter(); - interpreter.interpret(tree); + interpreter.interpret(tree) + .catch((error) => { + if (error instanceof Error) { + console.error(error.message); + } + else { + console.error("UnknownError:", error); + } + }); } catch (error) { if (error instanceof Error) { diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index 2a00342..45934ab 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -1,4 +1,5 @@ -import type { AST, Assign, BinaryOperation, CharConstant, Compound, GlobalScope, IntegerNumber, ProcedureCall, Program, RealNumber, StringConstant, UnaryOperation, Variable } from "../ast/nodes"; +import { AST, Assign, BinaryOperation, CharConstant, Compound, GlobalScope, IntegerNumber, ProcedureCall, Program, RealNumber, StringConstant, UnaryOperation, Variable } from "../ast/nodes"; +import { TypeOperationError } from "../errors/math"; import { TokenType } from "../lexer/tokens"; import { builtinProcedures } from "./builtins"; import { ActivationRecord, ActivationRecordType, CallStack } from "./stack"; @@ -118,22 +119,99 @@ class Interpreter { } } - private async visitBinaryOperation (node: BinaryOperation): Promise { - const node_left = (await this.visit(node.left)) as number; - const node_right = (await this.visit(node.right)) as number; + private async visitBinaryOperation (node: BinaryOperation): Promise { + const isChar = (node: BinaryOperation | IntegerNumber | UnaryOperation | Variable | RealNumber) => { + if (node instanceof Variable) { + const var_symbol = node.symbol_from_syntax_analyzer!; + return var_symbol.type === "caractère"; + } + + return node.type === "CharConstant"; + }; + + type HandledNode = { value: number, isTypeChar: true } | { value: number | string, isTypeChar: false } + const handleNode = async (node: BinaryOperation | IntegerNumber | UnaryOperation | Variable | RealNumber): Promise => { + const node_value = (await this.visit(node)) as number | string; + + if (isChar(node) && typeof node_value === "string") { + return { + value: node_value.charCodeAt(0), + isTypeChar: true + }; + } + + return { + value: node_value, + isTypeChar: false + }; + }; + + /** Whether it's not a character AND its value is a string. */ + const isString = (handled_node: HandledNode) => { + return !handled_node.isTypeChar && typeof handled_node.value === "string"; + }; + + let node_left = await handleNode(node.left); + let node_right = await handleNode(node.right); + + const handleOperation = (makeOperation: () => number | string) => { + if (node_left.isTypeChar) { + if (typeof node_right.value === "number") { + // Here, left is char so value is number and right is number. + return String.fromCharCode(makeOperation() as number); + } + else { + node_left = { // Not a char anymore. + isTypeChar: false, + value: String.fromCharCode(node_left.value) + }; + } + } + else if (node_right.isTypeChar) { + if (typeof node_left.value === "number") { + // Here, right is char so value is number and left is number. + return String.fromCharCode(makeOperation() as number); + } + else { + node_right = { // Not a char anymore. + isTypeChar: false, + value: String.fromCharCode(node_right.value) + }; + } + } + + // Force to run the operation again since + // values of `node_left` and `node_right` may have changed. + return makeOperation(); + }; + + const handleErrorsOnStrings = () => { + if (isString(node_left) || isString(node_right)) { + throw new TypeOperationError(node.token.type); + } + }; switch (node.token.type) { case TokenType.PLUS: - return node_left + node_right; + // @ts-expect-error : JS is able to add a string and a number. + return handleOperation(() => node_left.value + node_right.value); case TokenType.MINUS: - return node_left - node_right; + handleErrorsOnStrings(); + // @ts-expect-error : Both should be numbers. Note that `caractère` is a number. + return handleOperation(() => node_left.value - node_right.value); case TokenType.MUL: - return node_left * node_right; + handleErrorsOnStrings(); + // @ts-expect-error : Both should be numbers. Note that `caractère` is a number. + return handleOperation(() => node_left.value * node_right.value); case TokenType.DIV: - // Integer division. - return Math.floor(node_left / node_right); + handleErrorsOnStrings(); + // We use `Math.floor` to perform an integer division. + // @ts-expect-error : Both should be numbers. Note that `caractère` is a number. + return Math.floor(handleOperation(() => node_left.value / node_right.value)); case TokenType.MOD: - return node_left % node_right; + handleErrorsOnStrings(); + // @ts-expect-error : Both should be numbers. Note that `caractère` is a number. + return handleOperation(() => node_left.value % node_right.value); default: throw new Error("Invalid token type."); } diff --git a/src/symbols/visitor.ts b/src/symbols/visitor.ts index 08fefee..63611cc 100644 --- a/src/symbols/visitor.ts +++ b/src/symbols/visitor.ts @@ -1,4 +1,4 @@ -import { AST, Assign, BinaryOperation, Compound, GlobalScope, Procedure, ProcedureCall, Program, UnaryOperation, Variable, VariableDeclaration } from "../ast/nodes"; +import { AST, Assign, BinaryOperation, Compound, GlobalScope, Procedure, ProcedureCall, Program, StringConstant, UnaryOperation, Variable, VariableDeclaration } from "../ast/nodes"; import { TypeOperationError, TypeOperationVariableError } from "../errors/math"; import { UndeclaredVariableTypeError } from "../errors/variables"; import { TokenType } from "../lexer/tokens"; @@ -136,7 +136,7 @@ class SemanticAnalyzer { const operation = node.operation.type; // When one of the operands is a string, we throw an error. - if (node.left.type === "StringConstant" || node.right.type === "StringConstant") { + if (node.left instanceof StringConstant || node.right instanceof StringConstant) { throw new TypeOperationError(operation); }