Skip to content

Commit

Permalink
feat: handle references in procedures
Browse files Browse the repository at this point in the history
  • Loading branch information
Vexcited committed Dec 14, 2023
1 parent 31adb37 commit 0c1e88d
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 35 deletions.
21 changes: 18 additions & 3 deletions examples/procédure.fr
Original file line number Diff line number Diff line change
@@ -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
70 changes: 48 additions & 22 deletions src/ast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<ArgumentVariable> {
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;
Expand Down Expand Up @@ -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))
Expand Down
3 changes: 1 addition & 2 deletions src/ast/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export class If {

export class Procedure {
public type = "Procedure";
public args: VariableDeclaration[] = [];
public args: ArgumentVariable[] = [];

constructor (
public name: string,
Expand Down Expand Up @@ -128,7 +128,6 @@ export class ArgumentVariable {
public type_node: Type,
public method: "copy" | "reference"
) {}

}

export class VariableDeclaration {
Expand Down
17 changes: 17 additions & 0 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}

Expand Down
28 changes: 24 additions & 4 deletions src/interpreter/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ActivationRecordEffect>();
constructor (
public name: string,
public type: ActivationRecordType,
public members: Map<string, unknown> = 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);
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/lexer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ import {
FromToken,
ToToken,
StepToken,
RepeatToken
RepeatToken,
SemiColonToken
} from "./tokens";

const RESERVED_KEYWORDS = {
Expand Down Expand Up @@ -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();
Expand Down
7 changes: 7 additions & 0 deletions src/lexer/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export enum TokenType {
END = "END",

LINE_BREAK = "LINE_BREAK",
SEMI_COLON = "SEMI_COLON",
COLON = "COLON",
COMMA = "COMMA",

Expand Down Expand Up @@ -196,6 +197,12 @@ export class RParenToken extends BaseToken<string> {
}
}

export class SemiColonToken extends BaseToken<string> {
constructor () {
super(TokenType.SEMI_COLON, ";");
}
}

export class BooleanToken extends BaseToken<string> {
constructor () {
super(TokenType.BOOLEAN, "booléen");
Expand Down
8 changes: 7 additions & 1 deletion src/symbols/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/symbols/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit 0c1e88d

Please sign in to comment.