From 0c1e88d645e3a21ffca39f78bcc7ba073d3669f4 Mon Sep 17 00:00:00 2001 From: Mikkel RINGAUD Date: Thu, 14 Dec 2023 11:59:00 +0100 Subject: [PATCH] feat: handle references in procedures --- "examples/proc\303\251dure.fr" | 21 ++++++++-- src/ast/index.ts | 70 +++++++++++++++++++++++----------- src/ast/nodes.ts | 3 +- src/interpreter/index.ts | 17 +++++++++ src/interpreter/stack.ts | 28 ++++++++++++-- src/lexer/index.ts | 6 ++- src/lexer/tokens.ts | 7 ++++ src/symbols/builtins.ts | 8 +++- src/symbols/visitor.ts | 4 +- 9 files changed, 129 insertions(+), 35 deletions(-) diff --git "a/examples/proc\303\251dure.fr" "b/examples/proc\303\251dure.fr" index d8a73fa..2f93574 100644 --- "a/examples/proc\303\251dure.fr" +++ "b/examples/proc\303\251dure.fr" @@ -1,12 +1,27 @@ -procédure hello(nb: entier) +procédure world(; ch: chaîne) début - afficher "Un nombre:", nb + ch <- ch + " Et là c'est dans 'world()', eh oui !" +fin world + +procédure hello(nb: entier ; ch: chaîne) +début + afficher "Un nombre (passé en argument):", nb, "\n" + + nb <- 42 + afficher "Un nombre (modifié dans procédure):", nb, "\n" + + ch <- "Modifié depuis la procédure 'hello()' !" + world(; ch) fin hello programme World début avec mon_nombre : entier + une_chaîne : chaîne mon_nombre <- 10 - hello(mon_nombre) + hello(mon_nombre ; une_chaîne) + + afficher "Un nombre (non changé dans programme principal, car copie):", mon_nombre, "\n" + afficher "Une chaîne :", une_chaîne, "\n" fin World diff --git a/src/ast/index.ts b/src/ast/index.ts index 42407d7..933232b 100644 --- a/src/ast/index.ts +++ b/src/ast/index.ts @@ -140,18 +140,11 @@ export class Parser { this.eat(TokenType.PROCEDURE); const variable_node = this.variable(); const procedure_name = variable_node.value; - let args: ArgumentVariable[] = []; this.eat(TokenType.LPAREN); - // There's no arguments. - if (this.current_token?.type === TokenType.RPAREN) { - this.eat(TokenType.RPAREN); - } // Parse every arguments. - else { - args = this.argument_variables(); - this.eat(TokenType.RPAREN); - } + const args = this.argument_variables(); + this.eat(TokenType.RPAREN); // We expect a line break after the procedure definition. this.eat(TokenType.LINE_BREAK); @@ -178,16 +171,34 @@ export class Parser { * There can be a separator ";". * Everything before the separator is a COPY variable, * and everything after the separator is a REFERENCE variable. - * - * TODO: Add support for reference variables. */ private argument_variables (): Array { - const current_method: "copy" | "reference" = "copy"; - const var_nodes = [this.argument_variable(current_method)]; + const var_nodes: ArgumentVariable[] = []; - while (this.current_token?.type === TokenType.COMMA) { - this.eat(TokenType.COMMA); - var_nodes.push(this.argument_variable(current_method)); + const readArgumentsAndAppend = (method: "copy" | "reference", endToken: TokenType[]) => { + if (!this.current_token) throw new Error("Unexpected end of file."); + + if (!endToken.includes(this.current_token.type)) { + var_nodes.push(this.argument_variable(method)); + } + + while (this.current_token?.type === TokenType.COMMA) { + this.eat(TokenType.COMMA); + var_nodes.push(this.argument_variable(method)); + } + }; + + // SEMI_COLON can be an open token if there's no copy variables. + readArgumentsAndAppend("copy", [ + TokenType.RPAREN, + TokenType.SEMI_COLON + ]); + + if (this.current_token?.type === TokenType.SEMI_COLON) { + this.eat(TokenType.SEMI_COLON); + readArgumentsAndAppend("reference", [ + TokenType.RPAREN + ]); } return var_nodes; @@ -431,13 +442,28 @@ export class Parser { | CharConstant | StringConstant | BooleanConstant )[] = []; - if (this.current_token?.type !== TokenType.RPAREN) { - args.push(this.expr()); - } + const readArgumentsAndAppend = (additionalEndToken?: TokenType) => { + if (!this.current_token) throw new Error("Unexpected end of file."); + const endTokens = []; - while (this.current_token?.type === TokenType.COMMA) { - this.eat(TokenType.COMMA); - args.push(this.expr()); + if (additionalEndToken) endTokens.push(additionalEndToken); + if (!shouldSkipParenthesis) endTokens.push(TokenType.RPAREN); + + if (!endTokens.includes(this.current_token.type)) { + args.push(this.expr()); + } + + while (this.current_token?.type === TokenType.COMMA) { + this.eat(TokenType.COMMA); + args.push(this.expr()); + } + }; + + readArgumentsAndAppend(TokenType.SEMI_COLON); + + if (this.current_token?.type === TokenType.SEMI_COLON) { + this.eat(TokenType.SEMI_COLON); + readArgumentsAndAppend(); } if (!shouldSkipParenthesis || (shouldSkipParenthesis && this.current_token?.type === TokenType.RPAREN)) diff --git a/src/ast/nodes.ts b/src/ast/nodes.ts index 2525008..11a7c36 100644 --- a/src/ast/nodes.ts +++ b/src/ast/nodes.ts @@ -99,7 +99,7 @@ export class If { export class Procedure { public type = "Procedure"; - public args: VariableDeclaration[] = []; + public args: ArgumentVariable[] = []; constructor ( public name: string, @@ -128,7 +128,6 @@ export class ArgumentVariable { public type_node: Type, public method: "copy" | "reference" ) {} - } export class VariableDeclaration { diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index dee400f..a96ad5e 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -104,6 +104,7 @@ class Interpreter { // will always set it. const procedureSymbol = node.symbol_from_syntax_analyzer!; + const current_ar = this.call_stack.peek(); const ar = new ActivationRecord( procedureName, ActivationRecordType.PROCEDURE @@ -118,6 +119,22 @@ class Interpreter { for (let arg_index = 0; arg_index < formal_args.length; arg_index++) { const arg_symbol = formal_args[arg_index]; const arg_value = await this.visit(actual_args[arg_index]); + const arg = actual_args[arg_index]; + + // Handle `reference` variables. + if (arg instanceof Variable && arg_symbol.method === "reference") { + ar.defineEffect(arg_symbol.name, { + get() { + const variable_symbol = arg.symbol_from_syntax_analyzer!; + return current_ar.get(variable_symbol.name); + }, + set(value) { + const variable_symbol = arg.symbol_from_syntax_analyzer!; + current_ar.set(variable_symbol.name, value); + } + }); + } + ar.set(arg_symbol.name, arg_value); } diff --git a/src/interpreter/stack.ts b/src/interpreter/stack.ts index 0f3b9fa..ffef74e 100644 --- a/src/interpreter/stack.ts +++ b/src/interpreter/stack.ts @@ -3,19 +3,39 @@ export enum ActivationRecordType { PROCEDURE = "PROCEDURE" } +export interface ActivationRecordEffect { + set: (value: unknown) => void, + get: () => unknown +} + export class ActivationRecord { + private effects = new Map(); constructor ( public name: string, public type: ActivationRecordType, public members: Map = new Map() ) {} - public get (key: string) { - return this.members.get(key); + public get (key: string): unknown { + const effect = this.effects.get(key); + + if (effect) return effect.get(); + else return this.members.get(key); + } + + public set (key: string, value: unknown): void { + const effect = this.effects.get(key); + + if (effect) effect.set(value); + else this.members.set(key, value); } - public set (key: string, value: unknown) { - this.members.set(key, value); + /** + * Used to pass references to update a + * member in another activation record. + */ + public defineEffect (key: string, effect: ActivationRecordEffect): void { + this.effects.set(key, effect); } } diff --git a/src/lexer/index.ts b/src/lexer/index.ts index edf54c9..4ac7dee 100644 --- a/src/lexer/index.ts +++ b/src/lexer/index.ts @@ -47,7 +47,8 @@ import { FromToken, ToToken, StepToken, - RepeatToken + RepeatToken, + SemiColonToken } from "./tokens"; const RESERVED_KEYWORDS = { @@ -374,6 +375,9 @@ export class Lexer { case ":": this.advance(); return new ColonToken(); + case ";": + this.advance(); + return new SemiColonToken(); case "+": this.advance(); return new PlusToken(); diff --git a/src/lexer/tokens.ts b/src/lexer/tokens.ts index 3cd7c68..2eec4bb 100644 --- a/src/lexer/tokens.ts +++ b/src/lexer/tokens.ts @@ -42,6 +42,7 @@ export enum TokenType { END = "END", LINE_BREAK = "LINE_BREAK", + SEMI_COLON = "SEMI_COLON", COLON = "COLON", COMMA = "COMMA", @@ -196,6 +197,12 @@ export class RParenToken extends BaseToken { } } +export class SemiColonToken extends BaseToken { + constructor () { + super(TokenType.SEMI_COLON, ";"); + } +} + export class BooleanToken extends BaseToken { constructor () { super(TokenType.BOOLEAN, "booléen"); diff --git a/src/symbols/builtins.ts b/src/symbols/builtins.ts index 0e3a303..57a6380 100644 --- a/src/symbols/builtins.ts +++ b/src/symbols/builtins.ts @@ -19,6 +19,12 @@ export class VarSymbol extends BaseSymbol { } } +export class ArgumentVarSymbol extends VarSymbol { + constructor (name: string, type: BuiltinTypeSymbol, public method: "copy" | "reference") { + super(name, type); + } +} + export class ProcedureSymbol extends BaseSymbol { /** * Compound from the Procedure AST node. @@ -29,7 +35,7 @@ export class ProcedureSymbol extends BaseSymbol { */ public compound_from_node?: Compound; - constructor (name: string, public args: VarSymbol[] = []) { + constructor (name: string, public args: ArgumentVarSymbol[] = []) { super(name); } } diff --git a/src/symbols/visitor.ts b/src/symbols/visitor.ts index 4b72d3a..f0f6b37 100644 --- a/src/symbols/visitor.ts +++ b/src/symbols/visitor.ts @@ -2,7 +2,7 @@ import { AST, Assign, BinaryOperation, Compound, DoWhile, For, GlobalScope, If, import { TypeOperationError, TypeOperationVariableError } from "../errors/math"; import { UndeclaredVariableTypeError } from "../errors/variables"; import { TokenType } from "../lexer/tokens"; -import { BuiltinTypeSymbol, ProcedureSymbol, VarSymbol } from "./builtins"; +import { ArgumentVarSymbol, BuiltinTypeSymbol, ProcedureSymbol, VarSymbol } from "./builtins"; import ScopedSymbolTable from "./table"; class SemanticAnalyzer { @@ -105,7 +105,7 @@ class SemanticAnalyzer { throw new Error(`Type ${arg_type} not found.`); } - const var_symbol = new VarSymbol(arg_name, type_symbol); + const var_symbol = new ArgumentVarSymbol(arg_name, type_symbol, arg.method); this.current_scope.define(var_symbol); procedure_symbol.args.push(var_symbol); }