Skip to content

Commit

Permalink
feat: add support for si/sinon
Browse files Browse the repository at this point in the history
  • Loading branch information
Vexcited committed Dec 13, 2023
1 parent b87a20b commit df576b2
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 21 deletions.
11 changes: 11 additions & 0 deletions examples/conditions.fr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
programme Conditions
début
avec ma_condition : booléen

ma_condition <- vrai

si
ma_condition
alors afficher "c'est vrai !\n"
fin si
fin Conditions
73 changes: 57 additions & 16 deletions src/ast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
NotToken
} from "../lexer/tokens";

import { IntegerNumber, BinaryOperation, UnaryOperation, type AST, Compound, NoOp, Variable, Assign, Program, Type, VariableDeclaration, RealNumber, StringConstant, GlobalScope, Procedure, ArgumentVariable, ProcedureCall, CharConstant, BooleanConstant } from "./nodes";
import { IntegerNumber, BinaryOperation, UnaryOperation, type AST, Compound, NoOp, Variable, Assign, Program, Type, VariableDeclaration, RealNumber, StringConstant, GlobalScope, Procedure, ArgumentVariable, ProcedureCall, CharConstant, BooleanConstant, If } from "./nodes";

export class Parser {
/** Current token instance. */
Expand Down Expand Up @@ -318,30 +318,31 @@ export class Parser {
return root;
}

/** statement_list : statement | statement \n statement_list */
private statement_list (): Array<AST> {
const node = this.statement();
const results = [node];
/**
* statement_list : (statement)*
*
* Handles statements until it reaches an END token or an
* ELSE token (if it's inside an IF statement -> `insideIf === true`).
*/
private statement_list (insideIf = false): Array<AST> {
const results = [];

// After the first statement, we expect the token to be a line break.
// So we can loop on every statement until we reach the end of the program.
const isStatementsEnd = () => this.current_token?.type === TokenType.END || (
insideIf && this.current_token?.type === TokenType.ELSE
);

while (!isStatementsEnd()) {
// Eat every newline token between statements.
this.skip_newlines();

while (this.current_token?.type === TokenType.LINE_BREAK) {
// Eat the line break and move to next token.
this.eat(TokenType.LINE_BREAK);

// @ts-expect-error : We moved to next token.
// Check if the next token is the end of the program.
if (this.current_token?.type === TokenType.END) break;
// Check if the next token is the end of the statement list.
if (isStatementsEnd()) break;

results.push(this.statement());
}

if (this.current_token?.type !== TokenType.END) {
throw new Error(`Token attendu: END\nToken actuel: ${this.current_token?.type}`);
}

return results;
}

Expand Down Expand Up @@ -370,6 +371,10 @@ export class Parser {
}
}

else if (this.current_token?.type === TokenType.IF) {
return this.if_statement();
}

return this.empty();
}

Expand Down Expand Up @@ -427,6 +432,37 @@ export class Parser {
return node;
}

/**
* if_statement : SI expr ALORS statement_list (SINON statement_list)? FIN SI
*/
private if_statement (): If {
this.eat(TokenType.IF);
const condition = this.expr();

// Possible to add a newline before the `then` token.
this.skip_newlines();
this.eat(TokenType.THEN);

const main_statements = this.statement_list(true);
let else_statements: AST[] | undefined;

if (this.current_token?.type === TokenType.ELSE) {
this.eat(TokenType.ELSE);
else_statements = this.statement_list();
}

const node = new If(
condition,
main_statements,
else_statements
);

this.eat(TokenType.END);
this.eat(TokenType.IF);

return node;
}

/**
* Handles the variable node.
* variable: ID
Expand All @@ -451,6 +487,7 @@ export class Parser {
* Such as unary operations, numbers, parenthesis, etc.
*/
private factor (): BinaryOperation | IntegerNumber | RealNumber | UnaryOperation | Variable | CharConstant | StringConstant | BooleanConstant {
this.skip_newlines();
const token = this.current_token!;

switch (token.type) {
Expand Down Expand Up @@ -479,6 +516,10 @@ export class Parser {
case TokenType.LPAREN: {
this.eat(TokenType.LPAREN);
const node = this.expr();

// Possible to add a newline before the closing parenthesis.
this.skip_newlines();

this.eat(TokenType.RPAREN);
return node;
}
Expand Down
10 changes: 10 additions & 0 deletions src/ast/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ export class Program {
) {}
}

export class If {
public type = "If";

constructor (
public condition: BinaryOperation | IntegerNumber | UnaryOperation | Variable | CharConstant | StringConstant | BooleanConstant,
public main_statements: AST[] = [],
public else_statements: AST[] = []
) {}
}

export class Procedure {
public type = "Procedure";
public args: VariableDeclaration[] = [];
Expand Down
19 changes: 18 additions & 1 deletion src/interpreter/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AST, Assign, BinaryOperation, BooleanConstant, CharConstant, Compound, GlobalScope, IntegerNumber, ProcedureCall, Program, RealNumber, StringConstant, UnaryOperation, Variable } from "../ast/nodes";
import { AST, Assign, BinaryOperation, BooleanConstant, CharConstant, Compound, GlobalScope, If, IntegerNumber, ProcedureCall, Program, RealNumber, StringConstant, UnaryOperation, Variable } from "../ast/nodes";
import { TypeBooleanOperationError, TypeOperationError } from "../errors/math";
import { TokenType } from "../lexer/tokens";
import { builtinProcedures } from "./builtins";
Expand All @@ -24,6 +24,8 @@ class Interpreter {
return this.visitUnaryOperation(node as UnaryOperation);
case "Compound":
return this.visitCompound(node as Compound);
case "If":
return this.visitIf(node as If);
case "Assign":
return this.visitAssign(node as Assign);
case "Variable":
Expand Down Expand Up @@ -285,6 +287,21 @@ class Interpreter {
}
}

private async visitIf (node: If): Promise<void> {
const condition = await this.visit(node.condition);

if (condition) {
for (const statement of node.main_statements) {
await this.visit(statement);
}
}
else if (!condition && node.else_statements.length > 0) {
for (const statement of node.else_statements) {
await this.visit(statement);
}
}
}

private async visitAssign (node: Assign): Promise<void> {
const variableName = node.left.value;
const newVariableValue = await this.visit(node.right);
Expand Down
8 changes: 7 additions & 1 deletion src/lexer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ import {
GreaterThanToken,
EqualToken,
NotToken,
NotEqualToken
NotEqualToken,
IfToken,
ThenToken,
ElseToken
} from "./tokens";

const RESERVED_KEYWORDS = {
Expand All @@ -47,6 +50,9 @@ const RESERVED_KEYWORDS = {
"début": new BeginToken(),
"avec": new VariableDeclarationBlockToken(),
"fin": new EndToken(),
"si": new IfToken(),
"sinon": new ElseToken(),
"alors": new ThenToken(),

// Operator.
"mod": new ModToken(),
Expand Down
22 changes: 22 additions & 0 deletions src/lexer/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export enum TokenType {
MOD = "MOD",
NOT = "NOT",

IF = "IF",
ELSE = "ELSE",
THEN = "THEN",

EQUAL = "EQUAL",
NOT_EQUAL = "NOT_EQUAL",
LESS_THAN = "LOWER_THAN",
Expand Down Expand Up @@ -52,6 +56,24 @@ class BaseToken<T> {
) {}
}

export class IfToken extends BaseToken<string> {
constructor () {
super(TokenType.IF, "si");
}
}

export class ElseToken extends BaseToken<string> {
constructor () {
super(TokenType.ELSE, "sinon");
}
}

export class ThenToken extends BaseToken<string> {
constructor () {
super(TokenType.THEN, "alors");
}
}

export class IntegerToken extends BaseToken<string> {
constructor () {
super(TokenType.INTEGER, "entier");
Expand Down
31 changes: 28 additions & 3 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, StringConstant, UnaryOperation, Variable, VariableDeclaration } from "../ast/nodes";
import { AST, Assign, BinaryOperation, Compound, GlobalScope, If, 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 @@ -31,6 +31,8 @@ class SemanticAnalyzer {
return this.visitUnaryOperation(<UnaryOperation>node);
case "Compound":
return this.visitCompound(<Compound>node);
case "If":
return this.visitIf(<If>node);
case "VariableDeclaration":
return this.visitVariableDeclaration(<VariableDeclaration>node);
case "Assign":
Expand Down Expand Up @@ -163,13 +165,24 @@ class SemanticAnalyzer {

break;
}

/**
* When it's a "+" operation, we can have
* operations with every numbers but also strings.
* Conditional operators can be used on anything.
*
* A "+" operation can have
* operations with anything except booleans.
*/
case TokenType.EQUAL:
case TokenType.GREATER_THAN:
case TokenType.GREATER_THAN_OR_EQUAL:
case TokenType.LESS_THAN:
case TokenType.LESS_THAN_OR_EQUAL:
case TokenType.PLUS:
this.visit(node.left);
this.visit(node.right);
break;
default:
throw new Error("Erreur<symbols.visitBinaryOperation> de syntaxe.\ndebug: unknown binary operation.");
}
}

Expand All @@ -189,6 +202,18 @@ class SemanticAnalyzer {
}
}

private visitIf (node: If): void {
this.visit(node.condition);

for (const statement of node.main_statements) {
this.visit(statement);
}

for (const statement of node.else_statements) {
this.visit(statement);
}
}

private visitVariableDeclaration (node: VariableDeclaration): void {
const type_name = node.type_node.value;
const type_symbol: BuiltinTypeSymbol | undefined = this.current_scope!.lookup(type_name);
Expand Down

0 comments on commit df576b2

Please sign in to comment.