Skip to content

Commit

Permalink
feat: implement caractère (car) operations
Browse files Browse the repository at this point in the history
  • Loading branch information
Vexcited committed Dec 12, 2023
1 parent 291795d commit 6a8a0a2
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 20 deletions.
24 changes: 18 additions & 6 deletions examples/char.fr
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion examples/procédure.fr
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ début
avec mon_nombre : entier

mon_nombre <- 10
hello(b)
hello(mon_nombre)
fin World
10 changes: 9 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
98 changes: 88 additions & 10 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -118,22 +119,99 @@ class Interpreter {
}
}

private async visitBinaryOperation (node: BinaryOperation): Promise<number> {
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<number | string> {
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<HandledNode> => {
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.");
}
Expand Down
4 changes: 2 additions & 2 deletions src/symbols/visitor.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);
}

Expand Down

0 comments on commit 6a8a0a2

Please sign in to comment.