lazarus-lang 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lazarus_lang-1.0.0/LICENSE +21 -0
- lazarus_lang-1.0.0/PKG-INFO +98 -0
- lazarus_lang-1.0.0/README.md +77 -0
- lazarus_lang-1.0.0/lazarus.py +972 -0
- lazarus_lang-1.0.0/lazarus_lang.egg-info/PKG-INFO +98 -0
- lazarus_lang-1.0.0/lazarus_lang.egg-info/SOURCES.txt +9 -0
- lazarus_lang-1.0.0/lazarus_lang.egg-info/dependency_links.txt +1 -0
- lazarus_lang-1.0.0/lazarus_lang.egg-info/entry_points.txt +2 -0
- lazarus_lang-1.0.0/lazarus_lang.egg-info/top_level.txt +1 -0
- lazarus_lang-1.0.0/pyproject.toml +32 -0
- lazarus_lang-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ladji Doucaré
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lazarus-lang
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: LAZARUS — le langage de programmation de Ladji : accolades comme Java, simplicité de Python, mots-clés uniques (laz, fonk, kan, tanke...)
|
|
5
|
+
Author: Ladji Doucaré
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ladjidoucare994-boop/lazarus
|
|
8
|
+
Project-URL: Documentation, https://github.com/ladjidoucare994-boop/lazarus/blob/main/GUIDE_LAZARUS.md
|
|
9
|
+
Project-URL: Playground, https://ladjidoucare994-boop.github.io/lazarus/
|
|
10
|
+
Keywords: langage,programmation,interpreteur,lazarus,education,francais
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Software Development :: Interpreters
|
|
15
|
+
Classifier: Intended Audience :: Education
|
|
16
|
+
Classifier: Natural Language :: French
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# ⚡ LAZARUS
|
|
23
|
+
|
|
24
|
+
**Le langage de programmation créé par Ladji** — la structure de Java, la simplicité de Python, et des mots-clés uniques au monde.
|
|
25
|
+
|
|
26
|
+
*A programming language created by Ladji — Java's structure, Python's simplicity, and one-of-a-kind keywords. French-friendly error messages. See the [English section](#-english) below.*
|
|
27
|
+
|
|
28
|
+
```lazarus
|
|
29
|
+
laz nom = "monde"
|
|
30
|
+
|
|
31
|
+
fonk saluer(qui) {
|
|
32
|
+
rend "Bonjour " + qui + " !"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pou i dan 1..3 {
|
|
36
|
+
vox(saluer(nom))
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 🚀 Essayer tout de suite
|
|
41
|
+
|
|
42
|
+
**Dans le navigateur, sans rien installer :** ouvrez le playground en ligne (dossier [`docs/`](docs/index.html) — hébergé avec GitHub Pages).
|
|
43
|
+
|
|
44
|
+
**Sur votre machine :**
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install lazarus-lang # puis :
|
|
48
|
+
lazarus mon_programme.laz # exécuter un fichier
|
|
49
|
+
lazarus # mode interactif
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Ou sans pip, avec juste Python 3 :
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python3 lazarus.py exemples/demo.laz
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 📖 Le langage en 30 secondes
|
|
59
|
+
|
|
60
|
+
| LAZARUS | Signification |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `laz x = 5` | déclarer une variable |
|
|
63
|
+
| `vox("salut")` | afficher |
|
|
64
|
+
| `demand("Ton nom ? ")` | saisie clavier |
|
|
65
|
+
| `kan ... { } sinon { }` | si / sinon |
|
|
66
|
+
| `tanke ... { }` | tant que |
|
|
67
|
+
| `pou i dan 1..10 { }` | boucle pour |
|
|
68
|
+
| `fonk f(x) { rend x }` | fonction + retour |
|
|
69
|
+
| `kase` / `swiv` | break / continue |
|
|
70
|
+
| `vrai` / `faux` / `walu` | true / false / null |
|
|
71
|
+
| `et` / `ou` / `non` | and / or / not |
|
|
72
|
+
|
|
73
|
+
- Accolades `{ }` pour les blocs, **pas** de point-virgules
|
|
74
|
+
- Listes : `[1, 2, 3]`, intervalle : `1..10`
|
|
75
|
+
- 14 fonctions intégrées : `vox`, `demand`, `nombre`, `texte`, `taille`, `ajoute`, `retire`, `hasard`, `arondi`, `majus`, `minus`, `koupe`, `tri`, `tip`
|
|
76
|
+
- Messages d'erreur **en français**, clairs et pédagogiques
|
|
77
|
+
|
|
78
|
+
📚 **[Guide complet du langage → GUIDE_LAZARUS.md](GUIDE_LAZARUS.md)**
|
|
79
|
+
|
|
80
|
+
## 📂 Contenu du dépôt
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
lazarus.py L'interpréteur officiel (Python, zéro dépendance)
|
|
84
|
+
GUIDE_LAZARUS.md Le manuel complet du langage
|
|
85
|
+
exemples/ Programmes d'exemple (.laz)
|
|
86
|
+
docs/index.html Le playground web (moteur JavaScript complet)
|
|
87
|
+
pyproject.toml Package pip « lazarus-lang »
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 🌍 English
|
|
91
|
+
|
|
92
|
+
LAZARUS is a programming language with invented keywords (`laz` = let, `fonk` = function, `kan` = if, `tanke` = while, `pou...dan` = for...in, `vox` = print). Curly braces like Java, no semicolons like Python. It ships with a zero-dependency Python interpreter, a full in-browser playground (pure JavaScript), and beginner-friendly error messages in French. Try it: `pip install lazarus-lang`, then `lazarus examples/demo.laz`.
|
|
93
|
+
|
|
94
|
+
## Licence
|
|
95
|
+
|
|
96
|
+
MIT — libre et gratuit pour tout le monde, pour toujours.
|
|
97
|
+
|
|
98
|
+
*Créé avec ❤️ par Ladji Doucaré, 2026.*
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# ⚡ LAZARUS
|
|
2
|
+
|
|
3
|
+
**Le langage de programmation créé par Ladji** — la structure de Java, la simplicité de Python, et des mots-clés uniques au monde.
|
|
4
|
+
|
|
5
|
+
*A programming language created by Ladji — Java's structure, Python's simplicity, and one-of-a-kind keywords. French-friendly error messages. See the [English section](#-english) below.*
|
|
6
|
+
|
|
7
|
+
```lazarus
|
|
8
|
+
laz nom = "monde"
|
|
9
|
+
|
|
10
|
+
fonk saluer(qui) {
|
|
11
|
+
rend "Bonjour " + qui + " !"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pou i dan 1..3 {
|
|
15
|
+
vox(saluer(nom))
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 🚀 Essayer tout de suite
|
|
20
|
+
|
|
21
|
+
**Dans le navigateur, sans rien installer :** ouvrez le playground en ligne (dossier [`docs/`](docs/index.html) — hébergé avec GitHub Pages).
|
|
22
|
+
|
|
23
|
+
**Sur votre machine :**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install lazarus-lang # puis :
|
|
27
|
+
lazarus mon_programme.laz # exécuter un fichier
|
|
28
|
+
lazarus # mode interactif
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Ou sans pip, avec juste Python 3 :
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
python3 lazarus.py exemples/demo.laz
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 📖 Le langage en 30 secondes
|
|
38
|
+
|
|
39
|
+
| LAZARUS | Signification |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `laz x = 5` | déclarer une variable |
|
|
42
|
+
| `vox("salut")` | afficher |
|
|
43
|
+
| `demand("Ton nom ? ")` | saisie clavier |
|
|
44
|
+
| `kan ... { } sinon { }` | si / sinon |
|
|
45
|
+
| `tanke ... { }` | tant que |
|
|
46
|
+
| `pou i dan 1..10 { }` | boucle pour |
|
|
47
|
+
| `fonk f(x) { rend x }` | fonction + retour |
|
|
48
|
+
| `kase` / `swiv` | break / continue |
|
|
49
|
+
| `vrai` / `faux` / `walu` | true / false / null |
|
|
50
|
+
| `et` / `ou` / `non` | and / or / not |
|
|
51
|
+
|
|
52
|
+
- Accolades `{ }` pour les blocs, **pas** de point-virgules
|
|
53
|
+
- Listes : `[1, 2, 3]`, intervalle : `1..10`
|
|
54
|
+
- 14 fonctions intégrées : `vox`, `demand`, `nombre`, `texte`, `taille`, `ajoute`, `retire`, `hasard`, `arondi`, `majus`, `minus`, `koupe`, `tri`, `tip`
|
|
55
|
+
- Messages d'erreur **en français**, clairs et pédagogiques
|
|
56
|
+
|
|
57
|
+
📚 **[Guide complet du langage → GUIDE_LAZARUS.md](GUIDE_LAZARUS.md)**
|
|
58
|
+
|
|
59
|
+
## 📂 Contenu du dépôt
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
lazarus.py L'interpréteur officiel (Python, zéro dépendance)
|
|
63
|
+
GUIDE_LAZARUS.md Le manuel complet du langage
|
|
64
|
+
exemples/ Programmes d'exemple (.laz)
|
|
65
|
+
docs/index.html Le playground web (moteur JavaScript complet)
|
|
66
|
+
pyproject.toml Package pip « lazarus-lang »
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 🌍 English
|
|
70
|
+
|
|
71
|
+
LAZARUS is a programming language with invented keywords (`laz` = let, `fonk` = function, `kan` = if, `tanke` = while, `pou...dan` = for...in, `vox` = print). Curly braces like Java, no semicolons like Python. It ships with a zero-dependency Python interpreter, a full in-browser playground (pure JavaScript), and beginner-friendly error messages in French. Try it: `pip install lazarus-lang`, then `lazarus examples/demo.laz`.
|
|
72
|
+
|
|
73
|
+
## Licence
|
|
74
|
+
|
|
75
|
+
MIT — libre et gratuit pour tout le monde, pour toujours.
|
|
76
|
+
|
|
77
|
+
*Créé avec ❤️ par Ladji Doucaré, 2026.*
|
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
██╗ █████╗ ███████╗ █████╗ ██████╗ ██╗ ██╗███████╗
|
|
5
|
+
██║ ██╔══██╗╚══███╔╝██╔══██╗██╔══██╗██║ ██║██╔════╝
|
|
6
|
+
██║ ███████║ ███╔╝ ███████║██████╔╝██║ ██║███████╗
|
|
7
|
+
██║ ██╔══██║ ███╔╝ ██╔══██║██╔══██╗██║ ██║╚════██║
|
|
8
|
+
███████╗██║ ██║███████╗██║ ██║██║ ██║╚██████╔╝███████║
|
|
9
|
+
╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝
|
|
10
|
+
|
|
11
|
+
LAZARUS — Le langage de programmation de Ladji
|
|
12
|
+
Version 1.0
|
|
13
|
+
|
|
14
|
+
Syntaxe hybride Java + Python : des accolades { } mais pas de point-virgules.
|
|
15
|
+
Mots-clés inventés :
|
|
16
|
+
laz -> déclarer une variable
|
|
17
|
+
fonk -> définir une fonction
|
|
18
|
+
rend -> retourner une valeur
|
|
19
|
+
kan -> si (if)
|
|
20
|
+
sinon -> sinon (else) / sinon kan (else if)
|
|
21
|
+
tanke -> tant que (while)
|
|
22
|
+
pou..dan -> pour chaque (for)
|
|
23
|
+
kase -> casser la boucle (break)
|
|
24
|
+
swiv -> passer au suivant (continue)
|
|
25
|
+
vrai / faux / walu -> true / false / null
|
|
26
|
+
et / ou / non -> and / or / not
|
|
27
|
+
|
|
28
|
+
Utilisation :
|
|
29
|
+
python3 lazarus.py programme.laz (exécuter un fichier)
|
|
30
|
+
python3 lazarus.py (mode interactif)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import sys
|
|
34
|
+
import random
|
|
35
|
+
|
|
36
|
+
# ============================================================
|
|
37
|
+
# ERREURS
|
|
38
|
+
# ============================================================
|
|
39
|
+
|
|
40
|
+
class LazError(Exception):
|
|
41
|
+
def __init__(self, message, line=None):
|
|
42
|
+
self.message = message
|
|
43
|
+
self.line = line
|
|
44
|
+
super().__init__(message)
|
|
45
|
+
|
|
46
|
+
def __str__(self):
|
|
47
|
+
if self.line:
|
|
48
|
+
return f"✘ Erreur LAZARUS (ligne {self.line}) : {self.message}"
|
|
49
|
+
return f"✘ Erreur LAZARUS : {self.message}"
|
|
50
|
+
|
|
51
|
+
class ReturnEx(Exception):
|
|
52
|
+
def __init__(self, value):
|
|
53
|
+
self.value = value
|
|
54
|
+
|
|
55
|
+
class BreakEx(Exception):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
class ContinueEx(Exception):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
# ============================================================
|
|
62
|
+
# LEXER (découpe le code en jetons)
|
|
63
|
+
# ============================================================
|
|
64
|
+
|
|
65
|
+
KEYWORDS = {
|
|
66
|
+
'laz', 'fonk', 'rend', 'kan', 'sinon', 'tanke', 'pou', 'dan',
|
|
67
|
+
'vrai', 'faux', 'walu', 'et', 'ou', 'non', 'kase', 'swiv',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
TWO_CHAR_OPS = {'==', '!=', '<=', '>=', '&&', '||', '..'}
|
|
71
|
+
ONE_CHAR_OPS = {'+', '-', '*', '/', '%', '<', '>', '=', '(', ')',
|
|
72
|
+
'{', '}', '[', ']', ',', '!', ';'}
|
|
73
|
+
|
|
74
|
+
def tokenize(source):
|
|
75
|
+
tokens = []
|
|
76
|
+
i = 0
|
|
77
|
+
line = 1
|
|
78
|
+
n = len(source)
|
|
79
|
+
paren_depth = 0 # pas de NEWLINE à l'intérieur de ( ) ou [ ]
|
|
80
|
+
|
|
81
|
+
while i < n:
|
|
82
|
+
c = source[i]
|
|
83
|
+
|
|
84
|
+
# espaces
|
|
85
|
+
if c in ' \t\r':
|
|
86
|
+
i += 1
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# retour à la ligne = séparateur d'instructions
|
|
90
|
+
if c == '\n':
|
|
91
|
+
if paren_depth == 0:
|
|
92
|
+
tokens.append(('NEWLINE', None, line))
|
|
93
|
+
line += 1
|
|
94
|
+
i += 1
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
# commentaires # ... ou // ...
|
|
98
|
+
if c == '#' or (c == '/' and i + 1 < n and source[i+1] == '/'):
|
|
99
|
+
while i < n and source[i] != '\n':
|
|
100
|
+
i += 1
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# chaînes de caractères "..."
|
|
104
|
+
if c == '"':
|
|
105
|
+
i += 1
|
|
106
|
+
start_line = line
|
|
107
|
+
buf = []
|
|
108
|
+
while i < n and source[i] != '"':
|
|
109
|
+
ch = source[i]
|
|
110
|
+
if ch == '\n':
|
|
111
|
+
raise LazError('chaîne de caractères non fermée (il manque un ")', start_line)
|
|
112
|
+
if ch == '\\' and i + 1 < n:
|
|
113
|
+
nxt = source[i+1]
|
|
114
|
+
esc = {'n': '\n', 't': '\t', '"': '"', '\\': '\\'}
|
|
115
|
+
buf.append(esc.get(nxt, nxt))
|
|
116
|
+
i += 2
|
|
117
|
+
else:
|
|
118
|
+
buf.append(ch)
|
|
119
|
+
i += 1
|
|
120
|
+
if i >= n:
|
|
121
|
+
raise LazError('chaîne de caractères non fermée (il manque un ")', start_line)
|
|
122
|
+
i += 1 # sauter le " final
|
|
123
|
+
tokens.append(('STRING', ''.join(buf), start_line))
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
# nombres
|
|
127
|
+
if c.isdigit():
|
|
128
|
+
start = i
|
|
129
|
+
while i < n and source[i].isdigit():
|
|
130
|
+
i += 1
|
|
131
|
+
# partie décimale (attention à l'opérateur .. )
|
|
132
|
+
if i < n and source[i] == '.' and i + 1 < n and source[i+1].isdigit():
|
|
133
|
+
i += 1
|
|
134
|
+
while i < n and source[i].isdigit():
|
|
135
|
+
i += 1
|
|
136
|
+
num = source[start:i]
|
|
137
|
+
value = float(num) if '.' in num else int(num)
|
|
138
|
+
tokens.append(('NUMBER', value, line))
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
# identifiants et mots-clés
|
|
142
|
+
if c.isalpha() or c == '_':
|
|
143
|
+
start = i
|
|
144
|
+
while i < n and (source[i].isalnum() or source[i] == '_'):
|
|
145
|
+
i += 1
|
|
146
|
+
word = source[start:i]
|
|
147
|
+
if word in KEYWORDS:
|
|
148
|
+
tokens.append((word.upper(), word, line))
|
|
149
|
+
else:
|
|
150
|
+
tokens.append(('IDENT', word, line))
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
# opérateurs à deux caractères
|
|
154
|
+
if source[i:i+2] in TWO_CHAR_OPS:
|
|
155
|
+
tokens.append(('OP', source[i:i+2], line))
|
|
156
|
+
i += 2
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
# opérateurs à un caractère
|
|
160
|
+
if c in ONE_CHAR_OPS:
|
|
161
|
+
if c in '([':
|
|
162
|
+
paren_depth += 1
|
|
163
|
+
elif c in ')]':
|
|
164
|
+
paren_depth = max(0, paren_depth - 1)
|
|
165
|
+
if c == ';':
|
|
166
|
+
tokens.append(('NEWLINE', None, line))
|
|
167
|
+
else:
|
|
168
|
+
tokens.append(('OP', c, line))
|
|
169
|
+
i += 1
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
raise LazError(f"caractère inconnu : '{c}'", line)
|
|
173
|
+
|
|
174
|
+
tokens.append(('NEWLINE', None, line))
|
|
175
|
+
tokens.append(('EOF', None, line))
|
|
176
|
+
return tokens
|
|
177
|
+
|
|
178
|
+
# ============================================================
|
|
179
|
+
# PARSER (transforme les jetons en arbre de syntaxe)
|
|
180
|
+
# ============================================================
|
|
181
|
+
|
|
182
|
+
class Parser:
|
|
183
|
+
def __init__(self, tokens):
|
|
184
|
+
self.tokens = tokens
|
|
185
|
+
self.pos = 0
|
|
186
|
+
|
|
187
|
+
def peek(self):
|
|
188
|
+
return self.tokens[self.pos]
|
|
189
|
+
|
|
190
|
+
def next(self):
|
|
191
|
+
tok = self.tokens[self.pos]
|
|
192
|
+
self.pos += 1
|
|
193
|
+
return tok
|
|
194
|
+
|
|
195
|
+
def check(self, ttype, value=None):
|
|
196
|
+
tok = self.peek()
|
|
197
|
+
if tok[0] != ttype:
|
|
198
|
+
return False
|
|
199
|
+
if value is not None and tok[1] != value:
|
|
200
|
+
return False
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
def accept(self, ttype, value=None):
|
|
204
|
+
if self.check(ttype, value):
|
|
205
|
+
return self.next()
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
def expect(self, ttype, value=None, what=None):
|
|
209
|
+
tok = self.peek()
|
|
210
|
+
if not self.check(ttype, value):
|
|
211
|
+
attendu = what or value or ttype
|
|
212
|
+
trouve = tok[1] if tok[1] is not None else tok[0]
|
|
213
|
+
trouve = {'NEWLINE': 'une fin de ligne', 'EOF': 'la fin du fichier'}.get(trouve, trouve)
|
|
214
|
+
raise LazError(f"j'attendais « {attendu} » mais j'ai trouvé « {trouve} »", tok[2])
|
|
215
|
+
return self.next()
|
|
216
|
+
|
|
217
|
+
def skip_newlines(self):
|
|
218
|
+
while self.check('NEWLINE'):
|
|
219
|
+
self.next()
|
|
220
|
+
|
|
221
|
+
# ---------- programme ----------
|
|
222
|
+
|
|
223
|
+
def parse_program(self):
|
|
224
|
+
stmts = []
|
|
225
|
+
self.skip_newlines()
|
|
226
|
+
while not self.check('EOF'):
|
|
227
|
+
stmts.append(self.parse_statement())
|
|
228
|
+
self.skip_newlines()
|
|
229
|
+
return ('block', stmts)
|
|
230
|
+
|
|
231
|
+
def parse_block(self):
|
|
232
|
+
self.skip_newlines()
|
|
233
|
+
self.expect('OP', '{', '{')
|
|
234
|
+
stmts = []
|
|
235
|
+
self.skip_newlines()
|
|
236
|
+
while not self.check('OP', '}'):
|
|
237
|
+
if self.check('EOF'):
|
|
238
|
+
raise LazError("il manque une accolade fermante }", self.peek()[2])
|
|
239
|
+
stmts.append(self.parse_statement())
|
|
240
|
+
self.skip_newlines()
|
|
241
|
+
self.expect('OP', '}', '}')
|
|
242
|
+
return ('block', stmts)
|
|
243
|
+
|
|
244
|
+
# ---------- instructions ----------
|
|
245
|
+
|
|
246
|
+
def parse_statement(self):
|
|
247
|
+
tok = self.peek()
|
|
248
|
+
line = tok[2]
|
|
249
|
+
|
|
250
|
+
if self.accept('LAZ'):
|
|
251
|
+
name = self.expect('IDENT', what='un nom de variable')[1]
|
|
252
|
+
self.expect('OP', '=', '=')
|
|
253
|
+
value = self.parse_expression()
|
|
254
|
+
return ('declare', name, value, line)
|
|
255
|
+
|
|
256
|
+
if self.accept('FONK'):
|
|
257
|
+
name = self.expect('IDENT', what='un nom de fonction')[1]
|
|
258
|
+
self.expect('OP', '(', '(')
|
|
259
|
+
params = []
|
|
260
|
+
if not self.check('OP', ')'):
|
|
261
|
+
params.append(self.expect('IDENT', what='un paramètre')[1])
|
|
262
|
+
while self.accept('OP', ','):
|
|
263
|
+
params.append(self.expect('IDENT', what='un paramètre')[1])
|
|
264
|
+
self.expect('OP', ')', ')')
|
|
265
|
+
body = self.parse_block()
|
|
266
|
+
return ('fonk', name, params, body, line)
|
|
267
|
+
|
|
268
|
+
if self.accept('KAN'):
|
|
269
|
+
return self.parse_kan(line)
|
|
270
|
+
|
|
271
|
+
if self.accept('TANKE'):
|
|
272
|
+
cond = self.parse_expression()
|
|
273
|
+
body = self.parse_block()
|
|
274
|
+
return ('tanke', cond, body, line)
|
|
275
|
+
|
|
276
|
+
if self.accept('POU'):
|
|
277
|
+
var = self.expect('IDENT', what='un nom de variable')[1]
|
|
278
|
+
self.expect('DAN', what='dan')
|
|
279
|
+
iterable = self.parse_expression()
|
|
280
|
+
body = self.parse_block()
|
|
281
|
+
return ('pou', var, iterable, body, line)
|
|
282
|
+
|
|
283
|
+
if self.accept('REND'):
|
|
284
|
+
if self.check('NEWLINE') or self.check('OP', '}') or self.check('EOF'):
|
|
285
|
+
return ('rend', None, line)
|
|
286
|
+
value = self.parse_expression()
|
|
287
|
+
return ('rend', value, line)
|
|
288
|
+
|
|
289
|
+
if self.accept('KASE'):
|
|
290
|
+
return ('kase', line)
|
|
291
|
+
|
|
292
|
+
if self.accept('SWIV'):
|
|
293
|
+
return ('swiv', line)
|
|
294
|
+
|
|
295
|
+
# expression ou affectation
|
|
296
|
+
expr = self.parse_expression()
|
|
297
|
+
if self.check('OP', '='):
|
|
298
|
+
self.next()
|
|
299
|
+
value = self.parse_expression()
|
|
300
|
+
if expr[0] == 'var':
|
|
301
|
+
return ('assign', expr[1], value, line)
|
|
302
|
+
if expr[0] == 'index':
|
|
303
|
+
return ('assign_index', expr[1], expr[2], value, line)
|
|
304
|
+
raise LazError("on ne peut affecter une valeur qu'à une variable ou à un élément de liste", line)
|
|
305
|
+
return ('expr', expr, line)
|
|
306
|
+
|
|
307
|
+
def parse_kan(self, line):
|
|
308
|
+
cond = self.parse_expression()
|
|
309
|
+
body = self.parse_block()
|
|
310
|
+
else_branch = None
|
|
311
|
+
# autoriser un retour à la ligne avant « sinon »
|
|
312
|
+
save = self.pos
|
|
313
|
+
self.skip_newlines()
|
|
314
|
+
if self.accept('SINON'):
|
|
315
|
+
if self.accept('KAN'):
|
|
316
|
+
else_branch = ('block', [self.parse_kan(self.peek()[2])])
|
|
317
|
+
else:
|
|
318
|
+
else_branch = self.parse_block()
|
|
319
|
+
else:
|
|
320
|
+
self.pos = save
|
|
321
|
+
return ('kan', cond, body, else_branch, line)
|
|
322
|
+
|
|
323
|
+
# ---------- expressions ----------
|
|
324
|
+
|
|
325
|
+
def parse_expression(self):
|
|
326
|
+
return self.parse_or()
|
|
327
|
+
|
|
328
|
+
def parse_or(self):
|
|
329
|
+
left = self.parse_and()
|
|
330
|
+
while True:
|
|
331
|
+
if self.accept('OU') or (self.check('OP', '||') and self.next()):
|
|
332
|
+
right = self.parse_and()
|
|
333
|
+
left = ('or', left, right)
|
|
334
|
+
else:
|
|
335
|
+
break
|
|
336
|
+
return left
|
|
337
|
+
|
|
338
|
+
def parse_and(self):
|
|
339
|
+
left = self.parse_not()
|
|
340
|
+
while True:
|
|
341
|
+
if self.accept('ET') or (self.check('OP', '&&') and self.next()):
|
|
342
|
+
right = self.parse_not()
|
|
343
|
+
left = ('and', left, right)
|
|
344
|
+
else:
|
|
345
|
+
break
|
|
346
|
+
return left
|
|
347
|
+
|
|
348
|
+
def parse_not(self):
|
|
349
|
+
if self.accept('NON') or (self.check('OP', '!') and self.next()):
|
|
350
|
+
return ('not', self.parse_not())
|
|
351
|
+
return self.parse_comparison()
|
|
352
|
+
|
|
353
|
+
def parse_comparison(self):
|
|
354
|
+
left = self.parse_range()
|
|
355
|
+
tok = self.peek()
|
|
356
|
+
if tok[0] == 'OP' and tok[1] in ('==', '!=', '<', '>', '<=', '>='):
|
|
357
|
+
op = self.next()[1]
|
|
358
|
+
right = self.parse_range()
|
|
359
|
+
return ('cmp', op, left, right, tok[2])
|
|
360
|
+
return left
|
|
361
|
+
|
|
362
|
+
def parse_range(self):
|
|
363
|
+
left = self.parse_additive()
|
|
364
|
+
tok = self.peek()
|
|
365
|
+
if tok[0] == 'OP' and tok[1] == '..':
|
|
366
|
+
self.next()
|
|
367
|
+
right = self.parse_additive()
|
|
368
|
+
return ('range', left, right, tok[2])
|
|
369
|
+
return left
|
|
370
|
+
|
|
371
|
+
def parse_additive(self):
|
|
372
|
+
left = self.parse_term()
|
|
373
|
+
while True:
|
|
374
|
+
tok = self.peek()
|
|
375
|
+
if tok[0] == 'OP' and tok[1] in ('+', '-'):
|
|
376
|
+
op = self.next()[1]
|
|
377
|
+
right = self.parse_term()
|
|
378
|
+
left = ('binop', op, left, right, tok[2])
|
|
379
|
+
else:
|
|
380
|
+
break
|
|
381
|
+
return left
|
|
382
|
+
|
|
383
|
+
def parse_term(self):
|
|
384
|
+
left = self.parse_unary()
|
|
385
|
+
while True:
|
|
386
|
+
tok = self.peek()
|
|
387
|
+
if tok[0] == 'OP' and tok[1] in ('*', '/', '%'):
|
|
388
|
+
op = self.next()[1]
|
|
389
|
+
right = self.parse_unary()
|
|
390
|
+
left = ('binop', op, left, right, tok[2])
|
|
391
|
+
else:
|
|
392
|
+
break
|
|
393
|
+
return left
|
|
394
|
+
|
|
395
|
+
def parse_unary(self):
|
|
396
|
+
tok = self.peek()
|
|
397
|
+
if tok[0] == 'OP' and tok[1] == '-':
|
|
398
|
+
self.next()
|
|
399
|
+
return ('neg', self.parse_unary(), tok[2])
|
|
400
|
+
return self.parse_postfix()
|
|
401
|
+
|
|
402
|
+
def parse_postfix(self):
|
|
403
|
+
expr = self.parse_primary()
|
|
404
|
+
while True:
|
|
405
|
+
tok = self.peek()
|
|
406
|
+
if tok[0] == 'OP' and tok[1] == '(':
|
|
407
|
+
self.next()
|
|
408
|
+
args = []
|
|
409
|
+
if not self.check('OP', ')'):
|
|
410
|
+
args.append(self.parse_expression())
|
|
411
|
+
while self.accept('OP', ','):
|
|
412
|
+
args.append(self.parse_expression())
|
|
413
|
+
self.expect('OP', ')', ')')
|
|
414
|
+
expr = ('call', expr, args, tok[2])
|
|
415
|
+
elif tok[0] == 'OP' and tok[1] == '[':
|
|
416
|
+
self.next()
|
|
417
|
+
index = self.parse_expression()
|
|
418
|
+
self.expect('OP', ']', ']')
|
|
419
|
+
expr = ('index', expr, index, tok[2])
|
|
420
|
+
else:
|
|
421
|
+
break
|
|
422
|
+
return expr
|
|
423
|
+
|
|
424
|
+
def parse_primary(self):
|
|
425
|
+
tok = self.peek()
|
|
426
|
+
|
|
427
|
+
if tok[0] == 'NUMBER':
|
|
428
|
+
self.next()
|
|
429
|
+
return ('num', tok[1])
|
|
430
|
+
if tok[0] == 'STRING':
|
|
431
|
+
self.next()
|
|
432
|
+
return ('str', tok[1])
|
|
433
|
+
if tok[0] == 'VRAI':
|
|
434
|
+
self.next()
|
|
435
|
+
return ('bool', True)
|
|
436
|
+
if tok[0] == 'FAUX':
|
|
437
|
+
self.next()
|
|
438
|
+
return ('bool', False)
|
|
439
|
+
if tok[0] == 'WALU':
|
|
440
|
+
self.next()
|
|
441
|
+
return ('walu',)
|
|
442
|
+
if tok[0] == 'IDENT':
|
|
443
|
+
self.next()
|
|
444
|
+
return ('var', tok[1], tok[2])
|
|
445
|
+
if tok[0] == 'OP' and tok[1] == '(':
|
|
446
|
+
self.next()
|
|
447
|
+
expr = self.parse_expression()
|
|
448
|
+
self.expect('OP', ')', ')')
|
|
449
|
+
return expr
|
|
450
|
+
if tok[0] == 'OP' and tok[1] == '[':
|
|
451
|
+
self.next()
|
|
452
|
+
items = []
|
|
453
|
+
self.skip_newlines()
|
|
454
|
+
if not self.check('OP', ']'):
|
|
455
|
+
items.append(self.parse_expression())
|
|
456
|
+
while self.accept('OP', ','):
|
|
457
|
+
self.skip_newlines()
|
|
458
|
+
items.append(self.parse_expression())
|
|
459
|
+
self.skip_newlines()
|
|
460
|
+
self.expect('OP', ']', ']')
|
|
461
|
+
return ('list', items, tok[2])
|
|
462
|
+
|
|
463
|
+
trouve = tok[1] if tok[1] is not None else tok[0]
|
|
464
|
+
trouve = {'NEWLINE': 'une fin de ligne', 'EOF': 'la fin du fichier'}.get(trouve, trouve)
|
|
465
|
+
raise LazError(f"expression invalide, je ne comprends pas « {trouve} » (il manque peut-être une valeur)", tok[2])
|
|
466
|
+
|
|
467
|
+
# ============================================================
|
|
468
|
+
# ENVIRONNEMENT (portée des variables)
|
|
469
|
+
# ============================================================
|
|
470
|
+
|
|
471
|
+
class Env:
|
|
472
|
+
def __init__(self, parent=None):
|
|
473
|
+
self.vars = {}
|
|
474
|
+
self.parent = parent
|
|
475
|
+
|
|
476
|
+
def get(self, name, line=None):
|
|
477
|
+
env = self
|
|
478
|
+
while env is not None:
|
|
479
|
+
if name in env.vars:
|
|
480
|
+
return env.vars[name]
|
|
481
|
+
env = env.parent
|
|
482
|
+
raise LazError(f"la variable « {name} » n'existe pas (déclare-la avec : laz {name} = ...)", line)
|
|
483
|
+
|
|
484
|
+
def declare(self, name, value):
|
|
485
|
+
self.vars[name] = value
|
|
486
|
+
|
|
487
|
+
def assign(self, name, value, line=None):
|
|
488
|
+
env = self
|
|
489
|
+
while env is not None:
|
|
490
|
+
if name in env.vars:
|
|
491
|
+
env.vars[name] = value
|
|
492
|
+
return
|
|
493
|
+
env = env.parent
|
|
494
|
+
raise LazError(f"la variable « {name} » n'existe pas (déclare-la avec : laz {name} = ...)", line)
|
|
495
|
+
|
|
496
|
+
# ============================================================
|
|
497
|
+
# VALEURS ET FONCTIONS
|
|
498
|
+
# ============================================================
|
|
499
|
+
|
|
500
|
+
class LazFunction:
|
|
501
|
+
def __init__(self, name, params, body, env):
|
|
502
|
+
self.name = name
|
|
503
|
+
self.params = params
|
|
504
|
+
self.body = body
|
|
505
|
+
self.env = env
|
|
506
|
+
|
|
507
|
+
def to_text(value):
|
|
508
|
+
if value is None:
|
|
509
|
+
return 'walu'
|
|
510
|
+
if value is True:
|
|
511
|
+
return 'vrai'
|
|
512
|
+
if value is False:
|
|
513
|
+
return 'faux'
|
|
514
|
+
if isinstance(value, float) and value.is_integer():
|
|
515
|
+
return str(int(value))
|
|
516
|
+
if isinstance(value, list):
|
|
517
|
+
return '[' + ', '.join(
|
|
518
|
+
f'"{v}"' if isinstance(v, str) else to_text(v) for v in value
|
|
519
|
+
) + ']'
|
|
520
|
+
if isinstance(value, LazFunction):
|
|
521
|
+
return f'<fonk {value.name}>'
|
|
522
|
+
return str(value)
|
|
523
|
+
|
|
524
|
+
def is_truthy(value):
|
|
525
|
+
if value is None or value is False:
|
|
526
|
+
return False
|
|
527
|
+
if value is True:
|
|
528
|
+
return True
|
|
529
|
+
if isinstance(value, (int, float)):
|
|
530
|
+
return value != 0
|
|
531
|
+
if isinstance(value, (str, list)):
|
|
532
|
+
return len(value) > 0
|
|
533
|
+
return True
|
|
534
|
+
|
|
535
|
+
def check_number(value, line, contexte='cette opération'):
|
|
536
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
537
|
+
raise LazError(f"{contexte} demande un nombre, pas « {to_text(value)} »", line)
|
|
538
|
+
return value
|
|
539
|
+
|
|
540
|
+
# ============================================================
|
|
541
|
+
# FONCTIONS INTÉGRÉES
|
|
542
|
+
# ============================================================
|
|
543
|
+
|
|
544
|
+
def make_builtins(env):
|
|
545
|
+
|
|
546
|
+
def b_vox(args, line):
|
|
547
|
+
print(' '.join(to_text(a) for a in args))
|
|
548
|
+
return None
|
|
549
|
+
|
|
550
|
+
def b_demand(args, line):
|
|
551
|
+
prompt = to_text(args[0]) if args else ''
|
|
552
|
+
try:
|
|
553
|
+
return input(prompt)
|
|
554
|
+
except EOFError:
|
|
555
|
+
return ''
|
|
556
|
+
|
|
557
|
+
def b_nombre(args, line):
|
|
558
|
+
_need(args, 1, 'nombre', line)
|
|
559
|
+
v = args[0]
|
|
560
|
+
if isinstance(v, bool):
|
|
561
|
+
return 1 if v else 0
|
|
562
|
+
if isinstance(v, (int, float)):
|
|
563
|
+
return v
|
|
564
|
+
try:
|
|
565
|
+
f = float(str(v).strip().replace(',', '.'))
|
|
566
|
+
return int(f) if f.is_integer() else f
|
|
567
|
+
except (ValueError, TypeError):
|
|
568
|
+
raise LazError(f"impossible de convertir « {to_text(v)} » en nombre", line)
|
|
569
|
+
|
|
570
|
+
def b_texte(args, line):
|
|
571
|
+
_need(args, 1, 'texte', line)
|
|
572
|
+
return to_text(args[0])
|
|
573
|
+
|
|
574
|
+
def b_taille(args, line):
|
|
575
|
+
_need(args, 1, 'taille', line)
|
|
576
|
+
v = args[0]
|
|
577
|
+
if isinstance(v, (str, list)):
|
|
578
|
+
return len(v)
|
|
579
|
+
raise LazError("taille() fonctionne avec un texte ou une liste", line)
|
|
580
|
+
|
|
581
|
+
def b_ajoute(args, line):
|
|
582
|
+
_need(args, 2, 'ajoute', line)
|
|
583
|
+
if not isinstance(args[0], list):
|
|
584
|
+
raise LazError("ajoute() demande une liste en premier argument", line)
|
|
585
|
+
args[0].append(args[1])
|
|
586
|
+
return args[0]
|
|
587
|
+
|
|
588
|
+
def b_retire(args, line):
|
|
589
|
+
_need(args, 2, 'retire', line)
|
|
590
|
+
lst, idx = args[0], args[1]
|
|
591
|
+
if not isinstance(lst, list):
|
|
592
|
+
raise LazError("retire() demande une liste en premier argument", line)
|
|
593
|
+
idx = int(check_number(idx, line, 'retire()'))
|
|
594
|
+
if idx < -len(lst) or idx >= len(lst):
|
|
595
|
+
raise LazError(f"position {idx} hors de la liste (taille {len(lst)})", line)
|
|
596
|
+
return lst.pop(idx)
|
|
597
|
+
|
|
598
|
+
def b_hasard(args, line):
|
|
599
|
+
_need(args, 2, 'hasard', line)
|
|
600
|
+
a = int(check_number(args[0], line, 'hasard()'))
|
|
601
|
+
b = int(check_number(args[1], line, 'hasard()'))
|
|
602
|
+
return random.randint(min(a, b), max(a, b))
|
|
603
|
+
|
|
604
|
+
def b_arondi(args, line):
|
|
605
|
+
if not args:
|
|
606
|
+
_need(args, 1, 'arondi', line)
|
|
607
|
+
v = check_number(args[0], line, 'arondi()')
|
|
608
|
+
nd = int(check_number(args[1], line, 'arondi()')) if len(args) > 1 else 0
|
|
609
|
+
r = round(v, nd)
|
|
610
|
+
return int(r) if nd == 0 else r
|
|
611
|
+
|
|
612
|
+
def b_majus(args, line):
|
|
613
|
+
_need(args, 1, 'majus', line)
|
|
614
|
+
return to_text(args[0]).upper()
|
|
615
|
+
|
|
616
|
+
def b_minus(args, line):
|
|
617
|
+
_need(args, 1, 'minus', line)
|
|
618
|
+
return to_text(args[0]).lower()
|
|
619
|
+
|
|
620
|
+
def b_koupe(args, line):
|
|
621
|
+
_need(args, 2, 'koupe', line)
|
|
622
|
+
return to_text(args[0]).split(to_text(args[1]))
|
|
623
|
+
|
|
624
|
+
def b_tri(args, line):
|
|
625
|
+
_need(args, 1, 'tri', line)
|
|
626
|
+
if not isinstance(args[0], list):
|
|
627
|
+
raise LazError("tri() demande une liste", line)
|
|
628
|
+
try:
|
|
629
|
+
return sorted(args[0])
|
|
630
|
+
except TypeError:
|
|
631
|
+
raise LazError("tri() ne peut pas trier des types mélangés", line)
|
|
632
|
+
|
|
633
|
+
def b_tip(args, line):
|
|
634
|
+
_need(args, 1, 'tip', line)
|
|
635
|
+
v = args[0]
|
|
636
|
+
if v is None: return 'walu'
|
|
637
|
+
if isinstance(v, bool): return 'buli'
|
|
638
|
+
if isinstance(v, (int, float)): return 'nombre'
|
|
639
|
+
if isinstance(v, str): return 'texte'
|
|
640
|
+
if isinstance(v, list): return 'liste'
|
|
641
|
+
if isinstance(v, LazFunction): return 'fonk'
|
|
642
|
+
return 'inconnu'
|
|
643
|
+
|
|
644
|
+
def _need(args, count, name, line):
|
|
645
|
+
if len(args) < count:
|
|
646
|
+
raise LazError(f"{name}() demande au moins {count} argument(s), reçu {len(args)}", line)
|
|
647
|
+
|
|
648
|
+
builtins = {
|
|
649
|
+
'vox': b_vox, # afficher
|
|
650
|
+
'demand': b_demand, # demander une saisie au clavier
|
|
651
|
+
'nombre': b_nombre, # convertir en nombre
|
|
652
|
+
'texte': b_texte, # convertir en texte
|
|
653
|
+
'taille': b_taille, # longueur d'un texte ou d'une liste
|
|
654
|
+
'ajoute': b_ajoute, # ajouter à une liste
|
|
655
|
+
'retire': b_retire, # retirer d'une liste
|
|
656
|
+
'hasard': b_hasard, # nombre aléatoire entre a et b
|
|
657
|
+
'arondi': b_arondi, # arrondir
|
|
658
|
+
'majus': b_majus, # MAJUSCULES
|
|
659
|
+
'minus': b_minus, # minuscules
|
|
660
|
+
'koupe': b_koupe, # découper un texte en liste
|
|
661
|
+
'tri': b_tri, # trier une liste
|
|
662
|
+
'tip': b_tip, # type d'une valeur
|
|
663
|
+
}
|
|
664
|
+
for name, fn in builtins.items():
|
|
665
|
+
env.declare(name, ('builtin', name, fn))
|
|
666
|
+
|
|
667
|
+
# ============================================================
|
|
668
|
+
# INTERPRÉTEUR
|
|
669
|
+
# ============================================================
|
|
670
|
+
|
|
671
|
+
class Interpreter:
|
|
672
|
+
def __init__(self):
|
|
673
|
+
self.globals = Env()
|
|
674
|
+
make_builtins(self.globals)
|
|
675
|
+
|
|
676
|
+
def run(self, source):
|
|
677
|
+
tokens = tokenize(source)
|
|
678
|
+
ast = Parser(tokens).parse_program()
|
|
679
|
+
return self.exec_block(ast, self.globals)
|
|
680
|
+
|
|
681
|
+
def exec_block(self, block, env):
|
|
682
|
+
result = None
|
|
683
|
+
for stmt in block[1]:
|
|
684
|
+
result = self.exec_stmt(stmt, env)
|
|
685
|
+
return result
|
|
686
|
+
|
|
687
|
+
def exec_stmt(self, stmt, env):
|
|
688
|
+
kind = stmt[0]
|
|
689
|
+
|
|
690
|
+
if kind == 'declare':
|
|
691
|
+
_, name, value_node, line = stmt
|
|
692
|
+
env.declare(name, self.eval(value_node, env))
|
|
693
|
+
return None
|
|
694
|
+
|
|
695
|
+
if kind == 'assign':
|
|
696
|
+
_, name, value_node, line = stmt
|
|
697
|
+
env.assign(name, self.eval(value_node, env), line)
|
|
698
|
+
return None
|
|
699
|
+
|
|
700
|
+
if kind == 'assign_index':
|
|
701
|
+
_, target_node, index_node, value_node, line = stmt
|
|
702
|
+
target = self.eval(target_node, env)
|
|
703
|
+
index = self.eval(index_node, env)
|
|
704
|
+
value = self.eval(value_node, env)
|
|
705
|
+
if not isinstance(target, list):
|
|
706
|
+
raise LazError("on ne peut modifier par position que les listes", line)
|
|
707
|
+
idx = int(check_number(index, line, "l'indexation"))
|
|
708
|
+
if idx < -len(target) or idx >= len(target):
|
|
709
|
+
raise LazError(f"position {idx} hors de la liste (taille {len(target)})", line)
|
|
710
|
+
target[idx] = value
|
|
711
|
+
return None
|
|
712
|
+
|
|
713
|
+
if kind == 'fonk':
|
|
714
|
+
_, name, params, body, line = stmt
|
|
715
|
+
env.declare(name, LazFunction(name, params, body, env))
|
|
716
|
+
return None
|
|
717
|
+
|
|
718
|
+
if kind == 'kan':
|
|
719
|
+
_, cond, body, else_branch, line = stmt
|
|
720
|
+
if is_truthy(self.eval(cond, env)):
|
|
721
|
+
self.exec_block(body, env)
|
|
722
|
+
elif else_branch is not None:
|
|
723
|
+
self.exec_block(else_branch, env)
|
|
724
|
+
return None
|
|
725
|
+
|
|
726
|
+
if kind == 'tanke':
|
|
727
|
+
_, cond, body, line = stmt
|
|
728
|
+
while is_truthy(self.eval(cond, env)):
|
|
729
|
+
try:
|
|
730
|
+
self.exec_block(body, env)
|
|
731
|
+
except BreakEx:
|
|
732
|
+
break
|
|
733
|
+
except ContinueEx:
|
|
734
|
+
continue
|
|
735
|
+
return None
|
|
736
|
+
|
|
737
|
+
if kind == 'pou':
|
|
738
|
+
_, var, iterable_node, body, line = stmt
|
|
739
|
+
iterable = self.eval(iterable_node, env)
|
|
740
|
+
if isinstance(iterable, str):
|
|
741
|
+
iterable = list(iterable)
|
|
742
|
+
if not isinstance(iterable, list):
|
|
743
|
+
raise LazError("« pou ... dan ... » demande une liste, un intervalle (1..10) ou un texte", line)
|
|
744
|
+
env.declare(var, None)
|
|
745
|
+
for item in iterable:
|
|
746
|
+
env.vars[var] = item
|
|
747
|
+
try:
|
|
748
|
+
self.exec_block(body, env)
|
|
749
|
+
except BreakEx:
|
|
750
|
+
break
|
|
751
|
+
except ContinueEx:
|
|
752
|
+
continue
|
|
753
|
+
return None
|
|
754
|
+
|
|
755
|
+
if kind == 'rend':
|
|
756
|
+
_, value_node, line = stmt
|
|
757
|
+
value = self.eval(value_node, env) if value_node is not None else None
|
|
758
|
+
raise ReturnEx(value)
|
|
759
|
+
|
|
760
|
+
if kind == 'kase':
|
|
761
|
+
raise BreakEx()
|
|
762
|
+
|
|
763
|
+
if kind == 'swiv':
|
|
764
|
+
raise ContinueEx()
|
|
765
|
+
|
|
766
|
+
if kind == 'expr':
|
|
767
|
+
return self.eval(stmt[1], env)
|
|
768
|
+
|
|
769
|
+
raise LazError(f"instruction inconnue : {kind}")
|
|
770
|
+
|
|
771
|
+
# ---------- évaluation des expressions ----------
|
|
772
|
+
|
|
773
|
+
def eval(self, node, env):
|
|
774
|
+
kind = node[0]
|
|
775
|
+
|
|
776
|
+
if kind == 'num':
|
|
777
|
+
return node[1]
|
|
778
|
+
if kind == 'str':
|
|
779
|
+
return node[1]
|
|
780
|
+
if kind == 'bool':
|
|
781
|
+
return node[1]
|
|
782
|
+
if kind == 'walu':
|
|
783
|
+
return None
|
|
784
|
+
if kind == 'var':
|
|
785
|
+
return env.get(node[1], node[2])
|
|
786
|
+
if kind == 'list':
|
|
787
|
+
return [self.eval(item, env) for item in node[1]]
|
|
788
|
+
|
|
789
|
+
if kind == 'range':
|
|
790
|
+
_, start_node, end_node, line = node
|
|
791
|
+
start = int(check_number(self.eval(start_node, env), line, "l'intervalle .."))
|
|
792
|
+
end = int(check_number(self.eval(end_node, env), line, "l'intervalle .."))
|
|
793
|
+
step = 1 if end >= start else -1
|
|
794
|
+
return list(range(start, end + step, step))
|
|
795
|
+
|
|
796
|
+
if kind == 'or':
|
|
797
|
+
left = self.eval(node[1], env)
|
|
798
|
+
if is_truthy(left):
|
|
799
|
+
return left
|
|
800
|
+
return self.eval(node[2], env)
|
|
801
|
+
|
|
802
|
+
if kind == 'and':
|
|
803
|
+
left = self.eval(node[1], env)
|
|
804
|
+
if not is_truthy(left):
|
|
805
|
+
return left
|
|
806
|
+
return self.eval(node[2], env)
|
|
807
|
+
|
|
808
|
+
if kind == 'not':
|
|
809
|
+
return not is_truthy(self.eval(node[1], env))
|
|
810
|
+
|
|
811
|
+
if kind == 'neg':
|
|
812
|
+
_, operand, line = node
|
|
813
|
+
return -check_number(self.eval(operand, env), line, 'le signe -')
|
|
814
|
+
|
|
815
|
+
if kind == 'cmp':
|
|
816
|
+
_, op, left_node, right_node, line = node
|
|
817
|
+
left = self.eval(left_node, env)
|
|
818
|
+
right = self.eval(right_node, env)
|
|
819
|
+
if op == '==':
|
|
820
|
+
return left == right
|
|
821
|
+
if op == '!=':
|
|
822
|
+
return left != right
|
|
823
|
+
# comparaisons d'ordre : nombres entre eux, textes entre eux
|
|
824
|
+
if isinstance(left, str) and isinstance(right, str):
|
|
825
|
+
pass
|
|
826
|
+
else:
|
|
827
|
+
check_number(left, line, f"la comparaison {op}")
|
|
828
|
+
check_number(right, line, f"la comparaison {op}")
|
|
829
|
+
if op == '<': return left < right
|
|
830
|
+
if op == '>': return left > right
|
|
831
|
+
if op == '<=': return left <= right
|
|
832
|
+
if op == '>=': return left >= right
|
|
833
|
+
|
|
834
|
+
if kind == 'binop':
|
|
835
|
+
_, op, left_node, right_node, line = node
|
|
836
|
+
left = self.eval(left_node, env)
|
|
837
|
+
right = self.eval(right_node, env)
|
|
838
|
+
|
|
839
|
+
if op == '+':
|
|
840
|
+
if isinstance(left, str) or isinstance(right, str):
|
|
841
|
+
return to_text(left) + to_text(right)
|
|
842
|
+
if isinstance(left, list) and isinstance(right, list):
|
|
843
|
+
return left + right
|
|
844
|
+
check_number(left, line, "l'addition +")
|
|
845
|
+
check_number(right, line, "l'addition +")
|
|
846
|
+
return left + right
|
|
847
|
+
|
|
848
|
+
check_number(left, line, f"l'opération {op}")
|
|
849
|
+
check_number(right, line, f"l'opération {op}")
|
|
850
|
+
if op == '-':
|
|
851
|
+
return left - right
|
|
852
|
+
if op == '*':
|
|
853
|
+
return left * right
|
|
854
|
+
if op == '/':
|
|
855
|
+
if right == 0:
|
|
856
|
+
raise LazError("division par zéro impossible", line)
|
|
857
|
+
result = left / right
|
|
858
|
+
return int(result) if isinstance(result, float) and result.is_integer() else result
|
|
859
|
+
if op == '%':
|
|
860
|
+
if right == 0:
|
|
861
|
+
raise LazError("modulo par zéro impossible", line)
|
|
862
|
+
return left % right
|
|
863
|
+
|
|
864
|
+
if kind == 'index':
|
|
865
|
+
_, target_node, index_node, line = node
|
|
866
|
+
target = self.eval(target_node, env)
|
|
867
|
+
index = self.eval(index_node, env)
|
|
868
|
+
if not isinstance(target, (list, str)):
|
|
869
|
+
raise LazError("on ne peut indexer que les listes et les textes", line)
|
|
870
|
+
idx = int(check_number(index, line, "l'indexation"))
|
|
871
|
+
if idx < -len(target) or idx >= len(target):
|
|
872
|
+
raise LazError(f"position {idx} hors limites (taille {len(target)})", line)
|
|
873
|
+
return target[idx]
|
|
874
|
+
|
|
875
|
+
if kind == 'call':
|
|
876
|
+
_, callee_node, arg_nodes, line = node
|
|
877
|
+
callee = self.eval(callee_node, env)
|
|
878
|
+
args = [self.eval(a, env) for a in arg_nodes]
|
|
879
|
+
|
|
880
|
+
if isinstance(callee, tuple) and callee[0] == 'builtin':
|
|
881
|
+
return callee[2](args, line)
|
|
882
|
+
|
|
883
|
+
if isinstance(callee, LazFunction):
|
|
884
|
+
if len(args) != len(callee.params):
|
|
885
|
+
raise LazError(
|
|
886
|
+
f"la fonction « {callee.name} » attend {len(callee.params)} argument(s), reçu {len(args)}",
|
|
887
|
+
line)
|
|
888
|
+
call_env = Env(parent=callee.env)
|
|
889
|
+
for param, arg in zip(callee.params, args):
|
|
890
|
+
call_env.declare(param, arg)
|
|
891
|
+
try:
|
|
892
|
+
self.exec_block(callee.body, call_env)
|
|
893
|
+
except ReturnEx as ret:
|
|
894
|
+
return ret.value
|
|
895
|
+
return None
|
|
896
|
+
|
|
897
|
+
raise LazError(f"« {to_text(callee)} » n'est pas une fonction", line)
|
|
898
|
+
|
|
899
|
+
raise LazError(f"expression inconnue : {kind}")
|
|
900
|
+
|
|
901
|
+
# ============================================================
|
|
902
|
+
# POINT D'ENTRÉE
|
|
903
|
+
# ============================================================
|
|
904
|
+
|
|
905
|
+
BANNER = """
|
|
906
|
+
╔═══════════════════════════════════════════╗
|
|
907
|
+
║ LAZARUS v1.0 — le langage de Ladji ║
|
|
908
|
+
║ Tape ton code, ou « sortir » pour quitter ║
|
|
909
|
+
╚═══════════════════════════════════════════╝
|
|
910
|
+
"""
|
|
911
|
+
|
|
912
|
+
def repl():
|
|
913
|
+
print(BANNER)
|
|
914
|
+
interp = Interpreter()
|
|
915
|
+
buffer = []
|
|
916
|
+
while True:
|
|
917
|
+
try:
|
|
918
|
+
prompt = '....> ' if buffer else 'LAZ> '
|
|
919
|
+
line = input(prompt)
|
|
920
|
+
except (EOFError, KeyboardInterrupt):
|
|
921
|
+
print('\nAu revoir !')
|
|
922
|
+
break
|
|
923
|
+
|
|
924
|
+
if not buffer and line.strip() in ('sortir', 'quit', 'exit'):
|
|
925
|
+
print('Au revoir !')
|
|
926
|
+
break
|
|
927
|
+
|
|
928
|
+
buffer.append(line)
|
|
929
|
+
code = '\n'.join(buffer)
|
|
930
|
+
|
|
931
|
+
# attendre la fin des blocs { }
|
|
932
|
+
if code.count('{') > code.count('}'):
|
|
933
|
+
continue
|
|
934
|
+
|
|
935
|
+
buffer = []
|
|
936
|
+
if not code.strip():
|
|
937
|
+
continue
|
|
938
|
+
try:
|
|
939
|
+
result = interp.run(code)
|
|
940
|
+
if result is not None:
|
|
941
|
+
print(to_text(result))
|
|
942
|
+
except LazError as e:
|
|
943
|
+
print(e)
|
|
944
|
+
except RecursionError:
|
|
945
|
+
print('✘ Erreur LAZARUS : récursion trop profonde (boucle infinie ?)')
|
|
946
|
+
|
|
947
|
+
def run_file(path):
|
|
948
|
+
try:
|
|
949
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
950
|
+
source = f.read()
|
|
951
|
+
except FileNotFoundError:
|
|
952
|
+
print(f"✘ Fichier introuvable : {path}")
|
|
953
|
+
sys.exit(1)
|
|
954
|
+
|
|
955
|
+
interp = Interpreter()
|
|
956
|
+
try:
|
|
957
|
+
interp.run(source)
|
|
958
|
+
except LazError as e:
|
|
959
|
+
print(e)
|
|
960
|
+
sys.exit(1)
|
|
961
|
+
except RecursionError:
|
|
962
|
+
print('✘ Erreur LAZARUS : récursion trop profonde (boucle infinie ?)')
|
|
963
|
+
sys.exit(1)
|
|
964
|
+
|
|
965
|
+
def main():
|
|
966
|
+
if len(sys.argv) > 1:
|
|
967
|
+
run_file(sys.argv[1])
|
|
968
|
+
else:
|
|
969
|
+
repl()
|
|
970
|
+
|
|
971
|
+
if __name__ == '__main__':
|
|
972
|
+
main()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lazarus-lang
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: LAZARUS — le langage de programmation de Ladji : accolades comme Java, simplicité de Python, mots-clés uniques (laz, fonk, kan, tanke...)
|
|
5
|
+
Author: Ladji Doucaré
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ladjidoucare994-boop/lazarus
|
|
8
|
+
Project-URL: Documentation, https://github.com/ladjidoucare994-boop/lazarus/blob/main/GUIDE_LAZARUS.md
|
|
9
|
+
Project-URL: Playground, https://ladjidoucare994-boop.github.io/lazarus/
|
|
10
|
+
Keywords: langage,programmation,interpreteur,lazarus,education,francais
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Software Development :: Interpreters
|
|
15
|
+
Classifier: Intended Audience :: Education
|
|
16
|
+
Classifier: Natural Language :: French
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# ⚡ LAZARUS
|
|
23
|
+
|
|
24
|
+
**Le langage de programmation créé par Ladji** — la structure de Java, la simplicité de Python, et des mots-clés uniques au monde.
|
|
25
|
+
|
|
26
|
+
*A programming language created by Ladji — Java's structure, Python's simplicity, and one-of-a-kind keywords. French-friendly error messages. See the [English section](#-english) below.*
|
|
27
|
+
|
|
28
|
+
```lazarus
|
|
29
|
+
laz nom = "monde"
|
|
30
|
+
|
|
31
|
+
fonk saluer(qui) {
|
|
32
|
+
rend "Bonjour " + qui + " !"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pou i dan 1..3 {
|
|
36
|
+
vox(saluer(nom))
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 🚀 Essayer tout de suite
|
|
41
|
+
|
|
42
|
+
**Dans le navigateur, sans rien installer :** ouvrez le playground en ligne (dossier [`docs/`](docs/index.html) — hébergé avec GitHub Pages).
|
|
43
|
+
|
|
44
|
+
**Sur votre machine :**
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install lazarus-lang # puis :
|
|
48
|
+
lazarus mon_programme.laz # exécuter un fichier
|
|
49
|
+
lazarus # mode interactif
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Ou sans pip, avec juste Python 3 :
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python3 lazarus.py exemples/demo.laz
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 📖 Le langage en 30 secondes
|
|
59
|
+
|
|
60
|
+
| LAZARUS | Signification |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `laz x = 5` | déclarer une variable |
|
|
63
|
+
| `vox("salut")` | afficher |
|
|
64
|
+
| `demand("Ton nom ? ")` | saisie clavier |
|
|
65
|
+
| `kan ... { } sinon { }` | si / sinon |
|
|
66
|
+
| `tanke ... { }` | tant que |
|
|
67
|
+
| `pou i dan 1..10 { }` | boucle pour |
|
|
68
|
+
| `fonk f(x) { rend x }` | fonction + retour |
|
|
69
|
+
| `kase` / `swiv` | break / continue |
|
|
70
|
+
| `vrai` / `faux` / `walu` | true / false / null |
|
|
71
|
+
| `et` / `ou` / `non` | and / or / not |
|
|
72
|
+
|
|
73
|
+
- Accolades `{ }` pour les blocs, **pas** de point-virgules
|
|
74
|
+
- Listes : `[1, 2, 3]`, intervalle : `1..10`
|
|
75
|
+
- 14 fonctions intégrées : `vox`, `demand`, `nombre`, `texte`, `taille`, `ajoute`, `retire`, `hasard`, `arondi`, `majus`, `minus`, `koupe`, `tri`, `tip`
|
|
76
|
+
- Messages d'erreur **en français**, clairs et pédagogiques
|
|
77
|
+
|
|
78
|
+
📚 **[Guide complet du langage → GUIDE_LAZARUS.md](GUIDE_LAZARUS.md)**
|
|
79
|
+
|
|
80
|
+
## 📂 Contenu du dépôt
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
lazarus.py L'interpréteur officiel (Python, zéro dépendance)
|
|
84
|
+
GUIDE_LAZARUS.md Le manuel complet du langage
|
|
85
|
+
exemples/ Programmes d'exemple (.laz)
|
|
86
|
+
docs/index.html Le playground web (moteur JavaScript complet)
|
|
87
|
+
pyproject.toml Package pip « lazarus-lang »
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 🌍 English
|
|
91
|
+
|
|
92
|
+
LAZARUS is a programming language with invented keywords (`laz` = let, `fonk` = function, `kan` = if, `tanke` = while, `pou...dan` = for...in, `vox` = print). Curly braces like Java, no semicolons like Python. It ships with a zero-dependency Python interpreter, a full in-browser playground (pure JavaScript), and beginner-friendly error messages in French. Try it: `pip install lazarus-lang`, then `lazarus examples/demo.laz`.
|
|
93
|
+
|
|
94
|
+
## Licence
|
|
95
|
+
|
|
96
|
+
MIT — libre et gratuit pour tout le monde, pour toujours.
|
|
97
|
+
|
|
98
|
+
*Créé avec ❤️ par Ladji Doucaré, 2026.*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lazarus
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lazarus-lang"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "LAZARUS — le langage de programmation de Ladji : accolades comme Java, simplicité de Python, mots-clés uniques (laz, fonk, kan, tanke...)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{ name = "Ladji Doucaré" }]
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
keywords = ["langage", "programmation", "interpreteur", "lazarus", "education", "francais"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Topic :: Software Development :: Interpreters",
|
|
19
|
+
"Intended Audience :: Education",
|
|
20
|
+
"Natural Language :: French",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/ladjidoucare994-boop/lazarus"
|
|
25
|
+
Documentation = "https://github.com/ladjidoucare994-boop/lazarus/blob/main/GUIDE_LAZARUS.md"
|
|
26
|
+
Playground = "https://ladjidoucare994-boop.github.io/lazarus/"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
lazarus = "lazarus:main"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools]
|
|
32
|
+
py-modules = ["lazarus"]
|