Compare commits

..

15 Commits

13 changed files with 476 additions and 136 deletions

2
.gitignore vendored
View File

@ -9,4 +9,6 @@ rapport.log
rapport.pdf rapport.pdf
rapport.synctex.gz rapport.synctex.gz
rapport.toc rapport.toc
rapport.out
Projet_compilation_anthony_debucquoy_2025.tar.xz Projet_compilation_anthony_debucquoy_2025.tar.xz

View File

@ -7,6 +7,12 @@ ajouter 3 dans l;
afficher l; afficher l;
entier x = 3;
entier y = 4;
entier z = x + 3 * y + 6 / 3;
# #
# afficher x vaut y; # afficher x vaut y;
# afficher x ne vaut pas y; # afficher x ne vaut pas y;
@ -15,3 +21,11 @@ afficher l;
# afficher x + 3, 3 + y; # afficher x + 3, 3 + y;
# x = -x; # x = -x;
# afficher x; # afficher x;
# entier x = 2 + 3 * 4;
# entier y = 3 * 4 + 2;
# liste l = [1, 2, 3];
afficher l[2];

15
examples/boucles_var.spf Normal file
View File

@ -0,0 +1,15 @@
entier x = 0;
tant que x < 10 faire {
entier y = x;
x = x + 1;
afficher x, y;
}
afficher "yess";
pour chaque entier i dans [1:5] faire {
entier y = i;
y = y + i;
x = x + i;
afficher x, y;
}

14
examples/errors.spf Normal file
View File

@ -0,0 +1,14 @@
blop x = blop #SyntaxError
# afficher y; #UnknownVariable
# texte z;
# afficher z; #UninitializedVariable
# texte a;
# texte a; #AlreadyDefined
# entier b = "test"; #IncompatibleType
# liste c = [0, 1, 2];
# afficher c[10]; #IndexError

9
examples/lists.spf Normal file
View File

@ -0,0 +1,9 @@
liste premier = [1, 2, 3];
ajouter 4 dans premier;
liste deuxieme = [1:4];
liste troisième = [deuxieme[0]: 10];
liste quatrième = [1: troisième[6]];

View File

@ -3,13 +3,13 @@ entier age = 23;
booléen majeur = vrai; booléen majeur = vrai;
booléen ingénieur; booléen ingénieur;
majeur = faux; majeur = faux;
afficher nom, age, majeur; afficher nom, age, majeur;
#Ces lignes devraient donner une erreur # Ces lignes devraient donner une erreur
# majeur = 42; # majeur = 42;
# afficher majeur; # afficher majeur;

6
examples/test.spf Normal file
View File

@ -0,0 +1,6 @@
texte name = "Anthony";
si name ne vaut pas "Anthony" alors {
afficher "C'est pas moi";
} sinon {
afficher "C'est moi";
}

View File

@ -1,5 +1,7 @@
import sys import sys
from modules.errors import SPFUnknownVariable, SPFUninitializedVariable, SPFAlreadyDefined, SPFIncompatibleType, SPFIndexError
trace_format = '\033[1m -> ' trace_format = '\033[1m -> '
reset_format = '\033[0m' reset_format = '\033[0m'
@ -11,14 +13,16 @@ class Variables:
"booléen": bool, "booléen": bool,
"liste": list } "liste": list }
def __init__(self, typ, value = None): def __init__(self, typ, value=None):
assert typ in self.types.keys(), "Ce type de variable est inconnu" assert typ in self.types.keys(), "Ce type de variable est inconnu"
self.type = typ self.type = typ
assert self.checkType(value, typ), "Le type n'est pas équivalent" if not self.checkType(value, typ):
self.value = value if (value is not None) else self.default(typ) raise SPFIncompatibleType(value, self.type)
self.value = value
def set(self, value): def set(self, value):
assert self.checkType(value, self.type), "Le type n'est pas équivalent" if not self.checkType(value, self.type):
raise SPFIncompatibleType(value, self.type)
self.value = value self.value = value
def __str__(self): def __str__(self):
@ -49,19 +53,24 @@ class Variables:
self.trace = trace self.trace = trace
def get(self, name): def get(self, name):
assert name in self.variables, "la variable {name} n'éxiste pas" if name not in self.variables:
raise SPFUnknownVariable(name)
if self.variables[name].value == None:
raise SPFUninitializedVariable(name)
if self.trace: if self.trace:
print(f"{trace_format}accède {name}{reset_format}", file=sys.stderr) print(f"{trace_format}accède {name}{reset_format}", file=sys.stderr)
return self.variables[name].value return self.variables[name].value
def declare(self, typ, name, value=None): def declare(self, typ, name, value=None):
assert name not in self.variables, "la variable {name} existe déjà" if name in self.variables:
raise SPFAlreadyDefined(name)
self.variables[name] = self.Variable(typ, value) self.variables[name] = self.Variable(typ, value)
if self.trace: if self.trace:
print(f"{trace_format}déclare {name} = {value}{reset_format}", file=sys.stderr) print(f"{trace_format}déclare {name} = {value}{reset_format}", file=sys.stderr)
def assign(self, name, value): def assign(self, name, value):
assert name in self.variables, "la variable n'éxiste pas" if name not in self.variables:
raise SPFUnknownVariable(name)
self.variables[name].set(value) self.variables[name].set(value)
if self.trace: if self.trace:
print(f"{trace_format}modifie {name} = {value}{reset_format}", file=sys.stderr) print(f"{trace_format}modifie {name} = {value}{reset_format}", file=sys.stderr)

44
modules/errors.py Normal file
View File

@ -0,0 +1,44 @@
# args
# 0) variable name
# 1) list of lines of the stack trace
class SPFException(Exception):
def __init__(self, *args):
super().__init__(*args)
self.msg = "Une erreur est survenue"
self.errorline = None
def __str__(self):
return (f"[ligne {self.errorline}] " if self.errorline else "") + f"{self.msg}"
class SPFSyntaxError(SPFException):
def __init__(self, *args):
super().__init__(*args)
self.msg = "Une erreur de syntaxe est survenue"
class SPFUnknownVariable(SPFException):
def __init__(self, *args):
super().__init__(*args)
self.msg = f"la variable `{args[0]}` n'est pas déclarée"
class SPFUninitializedVariable(SPFException):
def __init__(self, *args):
super().__init__(*args)
self.msg = f"la variable `{args[0]}` n'est pas initialisée"
class SPFAlreadyDefined(SPFException):
def __init__(self, *args):
super().__init__(*args)
self.msg = f"la variable `{args[0]}` est déjà déclarée"
class SPFIncompatibleType(SPFException):
def __init__(self, *args):
super().__init__(*args)
self.msg = f"`{args[0]}` n'est pas de type `{args[1]}`"
class SPFIndexError(SPFException):
def __init__(self, *args):
super().__init__(*args)
self.msg = f"La liste `{args[0]}` ne posède pas d'élèment d'indexe {args[1]}"

BIN
rapport/images/notebook.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -6,10 +6,15 @@
\usepackage[pdftex]{graphicx} \usepackage[pdftex]{graphicx}
\usepackage{amsmath, amsfonts, amssymb, amsthm} \usepackage{amsmath, amsfonts, amssymb, amsthm}
\usepackage{fullpage} \usepackage{fullpage}
\usepackage[inline]{enumitem}
\usepackage{hyperref}
\usepackage{fancyvrb}
\title{Rapport du projet de compilation \\ Umons 2024-2025} \title{Rapport du projet de compilation \\ Umons 2024-2025}
\author{Debucquoy Anthony} \author{Debucquoy Anthony}
\DefineShortVerb{\|}
\begin{document} \begin{document}
\maketitle \maketitle
@ -20,46 +25,196 @@
\newpage \newpage
\section{Consigne} \section*{Préface}
% - Une description brève du projet;
\section{Grammaire}% Ce document retrace le développement d'un compilateur de \textit{\textbf{S}imple \textbf{P}rogramme
\label{sec:la grammaire} en \textbf{F}rançais} (SPF) dans le cadre du projet de compilation de L'\textit{Université de Mons}.
Ce projet est supposé être réalisé en groupe. Mais lorsque les consignes du projet été données,
j'ai rapidement souhaité me faire une idée de son ampleur en tentant une première version.
En restant sur ma lancée je suis arrivé très rapidement à un résultat satisfaisant à mes yeux et
pratiquement fonctionnelle (non exempt de bugs évidement). J'ai alors fait la demande à \textit{Mr.
DECAN Alexandre} notre assistant et la personne référente du projet.
Celui-ci m'a confirmé que je pourrais accomplir ce projet par moi-même.
\section{Consigne}
Il nous est demandé de développer un interpréteur pour le langage \textbf{SPF}, un langage de
programmation \textit{faiblement et statiquement typé} dans lequel les instructions sont en
français.
Voici une liste semi-exhaustive de ce qui nous est demandé.
\begin{itemize}
\item Les lignes commençant par le caractère |#| sont ignorés
\item Les instruction se termine par le caractère |;|
\item les types suivants doivent être implémentés:
\begin{enumerate*}
\item |entier|
\item |texte|
\item |liste|
\item |booléen|
\end{enumerate*}
\item une déclaration est de la forme |<type> <nom>;| ou |<type> <nom> = <expression>;|
\item une assignation est de la forme |<nom> = <expression;|
\item Les noms de variables peuvent contenir des accents
\item Il est possible de créer une liste d'éléments consécutive avec |[a:b]| où a et b sont
les bornes de la liste
\end{itemize}
Le langage contient également de nombreux opérateurs qui ne sont pas tous présents dans ce document,
car ils sont similaires à la majorité des langages. Il est cependant à noter que les mots sont traduit
de l'anglais au français. Par exemple, |true| deviens |vrai| en SPF.
On remarque également que l'accès à la liste commence avec un indice de 1. Ainsi, l'accès au deuxième
élément de la liste l (|liste l = [a, b, c];|) devra être effectué à l'aide de l'expression
|l[2]| et retournera l'élément |b|
L'ajout d'élément dans une liste se fait à l'aide de l'instruction\\
|ajouter <expression> dans <variable>|
L'affichage d'une expression à l'écran à l'aide de \\
|afficher <expression> (, <expression> ...);|
Finalement, les boucle |for| sont semblables à celle en python avec comme syntaxe \\
|<type> <variable> dans <expression> faire{...}|
On remarquera aussi que pour les variables initialisées dans les tests et boucles sont limités à leurs
portées.
\section{Grammaire}
% Une description de la grammaire implémentée et des points sensibles % Une description de la grammaire implémentée et des points sensibles
% la concernant; % la concernant;
\section{Approche}% J'ai tenté d'inclure un maximum d'éléments vu en cours dans la grammaire. Ça n'était pas le cas dans
\label{sec:Approche} un premier temps quand je tentais encore de voir l'étendue des capacités de la librairie et la grammaire a
donc été modifiée avec le temps. Je l'ai aussi réalisée bien avant que nous n'ayons vu tout le
contenu du cours. Je n'avais pas encore vu les grammaires LALR. Je pense malgré tout que
ça n'aurait pas changé grand chose à l'approche que j'ai actuellement.
Le programme est constitué d'instruction consécutive. La première ligne est donc naturellement.
\begin{Verbatim}[samepage=true, frame=single]
start: (instruction)*
\end{Verbatim}
où les instructions sont de la forme
\begin{Verbatim}[samepage=true, frame=single]
instruction: declaration ";"
| assignation ";"
| SHOW_KW expression ("," expression)* ";" -> afficher
| ADD_KW expression "dans" VARIABLE ";" -> append
| controls
\end{Verbatim}
Nous avons donc les 5 grandes catégories qui sont représentées par des instructions qui sont
terminées par un |;| (ou non dans le cas des instructions de contrôle)
Dans le but de respecter la consigne, le projet s'est déroulé avec l'option stricte activée tout le
long du développement. Ce qui m'a empêché de rédiger une grammaire ambigüe. Néanmoins, dans un
premier temps ma grammaire ne respectais pas du tout la priorité des opérateurs.
Après relecture du cours, j'ai pu ordonner mes opérateurs dans un tableau et ai réécrit ma grammaire
pour qu'elle soit non récursive gauche et que l'ordre des opérateurs soit respecté.
Pour lever la récursivité gauche, j'ai ajouté les analogues à chaque opérateur avec un u à la fin
(l'idée était d'avoir un \textbf{u} pour \textbf{u}nembiguoused mais par la suite je me suis rendu
compte que ce nom n'était pas très descriptif. Néanmoins, je ne l'ai pas changé par manque
d'imagination probablement.)
\begin{Verbatim}[samepage=true, frame=single]
logical: comparison logicalu?
logicalu: AND_OP logical
| OR_OP logical
\end{Verbatim}
Le reste du fichier est relativement standard et facile à comprendre.
\section{Approche}
% Une explication de votre approche pour gérer : % Une explication de votre approche pour gérer :
% Les variables, leur déclaration, leur type et laffichage via --trace; % Les variables, leur déclaration, leur type et laffichage via --trace;
% Le test conditionnel de la forme si/sinon; % Le test conditionnel de la forme si/sinon;
% La boucle tant que; % La boucle tant que;
% La boucle pour chaque, incluant la gestion de la variable tempo- % La boucle pour chaque, incluant la gestion de la variable temporaire.
% raire.
\section{Erreurs}% Ma première étape fut de prendre note de toutes les fonctions à implémenter dans le projet pour
\label{sec:Les erreurs} pouvoir m'y référer au fur et à mesure (Figure~\ref{fig:notebook})
\begin{figure}[h]
\centering
\includegraphics[width=0.8\textwidth]{images/notebook.jpg}
\caption{Note avant projet.}
\label{fig:notebook}
\end{figure}
Je souhaitais faire un système séparé pour gérer les variables. Au lieu de l'inclure dans le même
fichier, il m'a semblé judicieux d'en faire un système indépendant. (|./modules/Variables.py|)
Ce module est pratiquement un simple dictionnaire où les clés sont les noms des variables et les
valeurs sont des dictionnaires eux même contenant le type en clé et la valeur en valeur. Ce système
permet de rapidement trouver une variable dans le dictionnaire tout en gardant les informations sur
son contenue.
Le fait d'en faire un module séparé permet de bien définir toutes les interactions possibles avec
celle-ci (set, get, define, ...) tout en laissant la possibilité d'inclure des fonctionnalités
dynamiques (trace, dump, ...). Ce fût l'un des premiers élément développé qui est resté opérationnel
le long du projet.
Par la suite je me suis demandé comment devrais-je gérer les \textit{scope}. En effet, une
variable crée dans une boucle ne devrait, par exemple par rester disponible après la portée de
celle-ci. Je n'avais pas envisagé ce scénario au début de mon implémentation et j'ai donc trouvé
comme solution de faire une copie de mes variables disponibles avant le début de ma boucle et de les
restituer après.
Pour les tests conditionnel dans les boucles et les branchements, j'ai pu profiter de la puissance
de lark. \`A l'aide de |self.visit_children(el)|, et par effet cascade, cette expression sera
évaluée a |vrai| ou |faux|. En fonction de sa valeur et ainsi m'appuyer sur celle-ci pour
demander à python de faire mes branchements. J'ai été agréablement surpris de constater que cette expression
peut être évaluée plusieurs fois. Ce qui implique que ça ne pose pas de problème pour les boucles
|tant que|. Il suffit de faire correspondre les valeurs au code python.
Pour la gestion d'erreurs, j'ai, dans un premier temps, implémenté toutes les erreurs demandées dans
un module séparé. Par la suite, comme ces erreurs sont des classes, j'ai implémenté une classe
parent qui permet un affichage uniforme à l'aide de la méthode |__str__| qui lit l'attribut |msg|
(le message à afficher) et |errorline| (le numéro de ligne). Il suffit alors d'appeler ces erreurs
lorsqu'elle se produise.
\section{Erreurs}
% Une description brève des erreurs connues et des solutions envisagées; % Une description brève des erreurs connues et des solutions envisagées;
\section{Difficultés}% Je n'ai pour, l'instant pas trouvé d'erreurs évidentes. Lorsque c'était le cas, je me suis empressé de
\label{sec:Difficultés} tenter de les corriger et pour l'instant ça n'a pas été trop compliqué au point que je ne le mette
de côté.
\section{Difficultés}
% Une brève présentation des difficultés rencontrées et des solutions % Une brève présentation des difficultés rencontrées et des solutions
% implémentées/envisagées; % implémentées/envisagées;
\section{Utilisation de l'IA}% Le projet s'est principalement passé sans difficultés particulières. J'ai passé un petit moment à
\label{sec:Utilisation de l'IA} faire en sorte que ma grammaire soit sans ambigüité et que l'ordre des opérateurs soit correcte.
% Les points éventuels pour lesquels vous avez fait appel à une IA6 \section{Utilisation de l'IA}
% (ChatGPT, CoPilot, etc.);
\section{Répartition du travail}% Lorsque je travaille pour apprendre je ne souhaite en général pas utiliser d'IA dans le but de me
\label{sec:Répartition du travail} former mieux. Il m'arrive plus d'utiliser cet outil dans le but de me faciliter la tache pour un
travail fastidieux que je suis certains de savoir faire et dont je \textbf{peux} vérifier la fiabilité par
moi-même.
% La répartition du travail au sein du groupe. Ceci dis, lors de l'élaboration de ce projet, je me suis vu utiliser l'IA à un moment. J'ai tenté de
donner ma grammaire déjà écrite à chatgpt (modèle GPT-4o à travers \url{https://duck.ai/} ) en lui
demandant de me générer une série de programmes utilisant ce langage qui pourraient me servir de
test. L'objectif était d'avoir plus d'exemples de programmes que ceux données par la consigne.
Néanmoins, après très peu de temps je, me suis ravisé et ai simplement ignoré la réponse donnée, car les
propositions de programmes étaient bien trop simple et ne tentaient pas du tout de "piéger" mon
implémentation.
Pour être tout à fait transparent, c'est la seule et unique utilisation de l'IA que j'ai faite jusqu'à
maintenant, mais je compte utiliser LanguageTool à la fin de la rédaction de ce rapport pour m'aider
à corriger les éventuelles fautes commises dans ce document.
\section{Répartition du travail}
Comme précisé dans la préface de ce document, je me suis chargé de l'intégralité du projet. J'ai
commencé par me faire la main avec la librairie proposée (lark) par le tutoriel du parser json.
Ensuite le projet s'est très vite mis en place. Plusieurs révisions ont été nécessaires pour faire
correspondre le projet aux attentes, vous pouvez les consulter dans l'historique du repo git à
l'adresse \url{https://git.herisson.ovh/tonitch/compilation}
\end{document} \end{document}

View File

@ -1,40 +1,45 @@
start: (instruction)* start: (instruction)*
instruction: declaration ";" instruction: declaration ";"
| assignation ";" | assignation ";"
| SHOW_KW expression ("," expression)* ";" -> afficher | SHOW_KW expression ("," expression)* ";" -> afficher
| ADD_KW expression "dans" VARIABLE ";" -> append | ADD_KW expression "dans" VARIABLE ";" -> append
| controls | controls
expression: expressionleft // TODO: priorité des operator certainement fausse // rule finishing by u are "UnambigiousED"
| operator expression: logical
expressionleft: literal logical: comparison logicalu?
| list logicalu: AND_OP logical
| range | OR_OP logical
| VARIABLE -> variable
| "(" expression ")"
//any -> bool comparison: sumterm comparisonu?
operator: expressionleft SAME_OP expression -> equal comparisonu: SAME_OP comparison
| expressionleft DIFF_OP expression -> unequal | DIFF_OP comparison
//bool -> bool | LT_OP comparison
| expressionleft AND_OP expression -> and_op | LE_OP comparison
| expressionleft OR_OP expression -> or_op | GT_OP comparison
| NOT_OP expression -> not_op | GE_OP comparison
//int -> bool
| expressionleft LT_OP expression -> lt sumterm: multterm sumtermu?
| expressionleft LE_OP expression -> le sumtermu: PLUS_OP sumterm
| expressionleft GT_OP expression -> gt | MINUS_OP sumterm
| expressionleft GE_OP expression -> ge
//int -> int multterm: priority multtermu?
| expressionleft PLUS_OP expression -> plus multtermu: TIMES_OP multterm
| expressionleft MINUS_OP expression -> minus | DIVIDE_OP multterm
| expressionleft TIMES_OP expression -> time
| expressionleft DIVIDE_OP expression -> divide priority: finalterm
| NEG_OP expression -> neg | finalterm "[" expression "]" -> list_get
// string/list -> string/list | SIZE_OP finalterm
| SIZE_OP expression -> sizeof | NEG_OP finalterm
| NOT_OP finalterm
finalterm: "(" expression ")"
| literal
| list
| range
| VARIABLE -> variable
?type: BOOL_TYPE ?type: BOOL_TYPE
| INT_TYPE | INT_TYPE
@ -45,8 +50,8 @@ declaration: type VARIABLE (EQUAL_SIGN expression)?
assignation: VARIABLE EQUAL_SIGN expression assignation: VARIABLE EQUAL_SIGN expression
loop: "tant" "que" expression "faire" "{" (instruction)* "}" -> while_loop loop: "tant" "que" expression "faire" "{" instruction_seq "}" -> while_loop
| "pour" "chaque" type VARIABLE "dans" expression "faire" "{" (instruction)* "}" -> for_loop | "pour" "chaque" type VARIABLE "dans" expression "faire" "{" instruction_seq "}" -> for_loop
?literal: ENTIER -> entier ?literal: ENTIER -> entier
| booleen | booleen
@ -54,15 +59,16 @@ loop: "tant" "que" expression "faire" "{" (instruction)* "}" -> while_loop
list: "[" expression? ("," expression)* "]" list: "[" expression? ("," expression)* "]"
range: "[" expression? ":" expression? "]" range: "[" expression ":" expression "]"
controls: test controls: test
| loop | loop
test: "si" expression "alors" "{" instruction* "}" ("sinon" "{" instruction* "}")? test: "si" expression "alors" "{" instruction_seq "}" ("sinon" "{" instruction_seq "}")?
instruction_seq: (instruction*)
?booleen: TRUE_KW -> true ?booleen: TRUE_KW -> true
| FALSE_KW -> false | FALSE_KW -> false
TERMINAL: ";" TERMINAL: ";"
@ -99,6 +105,8 @@ GE_OP: ">="
CONC_OP: "+" CONC_OP: "+"
SIZE_OP: "taille" SIZE_OP: "taille"
LBRAC: "["
RBRAC: "]"
ADD_KW: "ajouter" ADD_KW: "ajouter"
SHOW_KW: "afficher" SHOW_KW: "afficher"

206
spf.py
View File

@ -8,6 +8,7 @@ import lark
import sys import sys
from enum import Enum from enum import Enum
from modules.Variables import Variables from modules.Variables import Variables
from modules.errors import *
class SPFInterpreter(lark.visitors.Interpreter): class SPFInterpreter(lark.visitors.Interpreter):
def __init__(self, trace=False): def __init__(self, trace=False):
@ -15,13 +16,32 @@ class SPFInterpreter(lark.visitors.Interpreter):
self.variables = Variables(trace) self.variables = Variables(trace)
def while_loop(self, el): def while_loop(self, el):
print("TODO: while") old = self.variables.variables.copy()
cond = el.children[0] while self.visit_children(el.children[0])[0]:
instr = el.children[1:] self.visit_children(el.children[1])
print(cond.pretty()) self.variables.variables = old.copy()
def for_loop(self, el): def for_loop(self, el):
print("TODO: for") type = el.children[0].value
name = el.children[1].value
old = self.variables.variables.copy()
try:
self.variables.declare(type, name)
except SPFException as e:
e.errorline = el.meta.line
raise e
old_inloop = self.variables.variables.copy()
target = self.visit_children(el.children[2])[0]
for i in target:
try:
self.variables.assign(name, i)
except SPFException as e:
e.errorline = el.meta.line
raise e
self.visit_children(el.children[3])
self.variables.variables = old_inloop.copy()
self.variables.variables = old.copy()
def afficher(self, el): def afficher(self, el):
ligne = "" ligne = ""
@ -31,87 +51,123 @@ class SPFInterpreter(lark.visitors.Interpreter):
def append(self, el): def append(self, el):
(_, toadd, var) = self.visit_children(el); (_, toadd, var) = self.visit_children(el);
var_val = self.variables.get(var.value) try:
var_val = self.variables.get(var.value)
except SPFException as e:
e.errorline = el.meta.line
raise e
var_val.append(toadd) var_val.append(toadd)
def declaration(self, el): def declaration(self, el):
type = el.children[0].value type = el.children[0].value
name = el.children[1].value name = el.children[1].value
value = self.visit_children(el.children[3])[0] if len(el.children) >= 3 else None value = self.visit_children(el.children[3])[0] if len(el.children) >= 3 else None
self.variables.declare(type, name, value) try:
self.variables.declare(type, name, value)
except SPFException as e:
e.errorline = el.meta.line
raise e
def assignation(self, el): def assignation(self, el):
name = el.children[0].value name = el.children[0].value
assert el.children[1].value == "=" and el.children[2].data == "expression", "Unexpected" assert el.children[1].value == "=" and el.children[2].data == "expression", "Unexpected"
value = self.visit_children(el.children[2])[0] value = self.visit_children(el.children[2])[0]
self.variables.assign(name, value) try:
self.variables.assign(name, value)
def equal(self, el): except SPFException as e:
(left, sign, right) = self.visit_children(el) e.errorline = el.meta.line
return left == right raise e
def unequal(self, el):
(left, sign, right) = self.visit_children(el)
return left != right
def and_op(self, el):
(left, sign, right) = self.visit_children(el)
return left and right
def or_op(self, el):
(left, sign, right) = self.visit_children(el)
return left or right
def not_op(self, el):
(sign, right) = self.visit_children(el)
return not right
def lt(self, el):
(left, sign, right) = self.visit_children(el)
return left < right
def le(self, el):
(left, sign, right) = self.visit_children(el)
return left <= right
def gt(self, el):
(left, sign, right) = self.visit_children(el)
return left > right
def ge(self, el):
(left, sign, right) = self.visit_children(el)
return left >= right
def plus(self, el):
(left, sign, right) = self.visit_children(el)
return left + right # Cool ça fonctionne pour les str
def minus(self, el):
(left, sign, right) = self.visit_children(el)
return left - right
def time(self, el):
(left, sign, right) = self.visit_children(el)
return left * right
def divide(self, el):
(left, sign, right) = self.visit_children(el)
return left / right
neg = lambda self, el:-self.visit_children(el)[1]
sizeof = lambda self, el:len(self.visit_children(el)[1])
def expression(self, el): def expression(self, el):
return self.visit_children(el)[0] return self.visit_children(el)[0]
def expressionleft(self, el): def logical(self, el):
result = self.visit_children(el)
if len(result) < 2:
return result[0]
if result[1][0].type == "AND_OP":
return result[0] and result[1][1]
elif result[1][0].type == "OR_OP":
return result[0] or result[1][1]
assert "Unreachable"
def comparison(self, el):
result = self.visit_children(el)
if len(result) < 2:
return result[0]
if result[1][0].type == "SAME_OP":
return result[0] == result[1][1]
elif result[1][0].type == "DIFF_OP":
return result[0] != result[1][1]
elif result[1][0].type == "LT_OP":
return result[0] < result[1][1]
elif result[1][0].type == "LE_OP":
return result[0] <= result[1][1]
elif result[1][0].type == "GT_OP":
return result[0] > result[1][1]
elif result[1][0].type == "GE_OP":
return result[0] >= result[1][1]
assert "Unreachable"
def sumterm(self, el):
result = self.visit_children(el)
if len(result) < 2:
return result[0]
if result[1][0].type == "PLUS_OP":
return result[0] + result[1][1]
elif result[1][0].type == "MINUS_OP":
return result[0] - result[1][1]
assert "Unreachable"
def multterm(self, el):
result = self.visit_children(el)
if len(result) < 2:
return result[0]
if result[1][0].type == "TIMES_OP":
return result[0] * result[1][1]
elif result[1][0].type == "DIVIDE_OP": # Division entière car nous ne gérons pas les flotants
return result[0] // result[1][1]
assert "Unreachable"
def priority(self, el):
result = self.visit_children(el)
if len(result) < 2:
return result[0]
elif result[0].type == "SIZE_OP":
return len(result[1])
elif result[0].type == "NEG_OP":
return -result[1]
elif result[0].type == "NOT_OP":
return not result[1]
def list_get(self, el):
result = self.visit_children(el)
try:
return result[0][result[1] - 1] # Index start at 1 (like lua)
except IndexError:
e = SPFIndexError(result[0], result[1])
e.errorline = el.meta.line
raise e
def finalterm(self, el):
return self.visit_children(el)[0] return self.visit_children(el)[0]
def variable(self, el): def variable(self, el):
return self.variables.get(el.children[0].value) try:
return self.variables.get(el.children[0].value)
except SPFException as e:
e.errorline = el.meta.line
raise e
def test(self,el):
old = self.variables.variables.copy()
if self.visit_children(el.children[0])[0]:
self.visit_children(el.children[1])
elif len(el.children) >= 3:
self.visit_children(el.children[2])
self.variables.variables = old.copy()
# Literals # Literals
string = lambda self, el: el.children[0][1:-1] string = lambda self, el: el.children[0][1:-1]
@ -119,6 +175,10 @@ class SPFInterpreter(lark.visitors.Interpreter):
true = lambda self, _: True true = lambda self, _: True
false = lambda self, _: False false = lambda self, _: False
def range(self, el):
(left, right) = self.visit_children(el)
return list(range(left, right+1))
def dump(self): def dump(self):
self.variables.dump() self.variables.dump()
@ -139,11 +199,16 @@ def main():
args = arg_parser.parse_args() args = arg_parser.parse_args()
with open("spf.lark") as grammar: with open("spf.lark") as grammar:
spf_parser = lark.Lark(grammar, parser="lalr", strict=True, debug=True) spf_parser = lark.Lark(grammar, parser="lalr", strict=True, debug=True, propagate_positions=True)
with open(args.spf_file) as spf_input: with open(args.spf_file) as spf_input:
program = spf_input.read() program = spf_input.read()
parsed = spf_parser.parse(program) try:
parsed = spf_parser.parse(program)
except lark.UnexpectedInput as u:
e = SPFSyntaxError()
e.errorline = u.line
raise e
if args.pretty: if args.pretty:
print(parsed.pretty()) print(parsed.pretty())
@ -155,7 +220,6 @@ def main():
if args.dump: if args.dump:
interpreter.dump() interpreter.dump()
if __name__ == "__main__": if __name__ == "__main__":
main() main()