dshellInterpreter 0.1.0__py3-none-any.whl
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.
Potentially problematic release.
This version of dshellInterpreter might be problematic. Click here for more details.
- Dshell/DISCORD_COMMANDS/__init__.py +2 -0
- Dshell/DISCORD_COMMANDS/dshell_channel.py +60 -0
- Dshell/DISCORD_COMMANDS/dshell_message.py +61 -0
- Dshell/_DshellInterpreteur/__init__.py +1 -0
- Dshell/_DshellInterpreteur/dshell_interpreter.py +267 -0
- Dshell/_DshellParser/__init__.py +2 -0
- Dshell/_DshellParser/ast_nodes.py +204 -0
- Dshell/_DshellParser/dshell_parser.py +308 -0
- Dshell/_DshellTokenizer/__init__.py +4 -0
- Dshell/_DshellTokenizer/dshell_keywords.py +125 -0
- Dshell/_DshellTokenizer/dshell_token_type.py +39 -0
- Dshell/_DshellTokenizer/dshell_tokenizer.py +122 -0
- Dshell/__init__.py +1 -0
- dshellinterpreter-0.1.0.dist-info/METADATA +34 -0
- dshellinterpreter-0.1.0.dist-info/RECORD +18 -0
- dshellinterpreter-0.1.0.dist-info/WHEEL +5 -0
- dshellinterpreter-0.1.0.dist-info/licenses/LICENSE +21 -0
- dshellinterpreter-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from asyncio import sleep
|
|
2
|
+
from re import search
|
|
3
|
+
|
|
4
|
+
from discord import MISSING
|
|
5
|
+
from discord.abc import GuildChannel
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'dshell_create_text_channel',
|
|
9
|
+
'dshell_delete_channel',
|
|
10
|
+
'dshell_delete_channels'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
async def dshell_create_text_channel(ctx: GuildChannel, name, category=None, position=MISSING, slowmode=MISSING,
|
|
14
|
+
topic=MISSING, nsfw=MISSING):
|
|
15
|
+
"""
|
|
16
|
+
Crée un salon textuel sur le serveur
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
channel_category = ctx.guild.get_channel(category)
|
|
20
|
+
|
|
21
|
+
created_channel = await ctx.guild.create_text_channel(name,
|
|
22
|
+
category=channel_category,
|
|
23
|
+
position=position,
|
|
24
|
+
slowmode_delay=slowmode,
|
|
25
|
+
topic=topic,
|
|
26
|
+
nsfw=nsfw)
|
|
27
|
+
|
|
28
|
+
return created_channel.id
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def dshell_delete_channel(ctx: GuildChannel, channel=None, reason=None, timeout=0):
|
|
32
|
+
"""
|
|
33
|
+
Supprime un salon.
|
|
34
|
+
Possibilité de lui rajouter un temps d'attente avant qu'il ne le supprime (en seconde)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
channel_to_delete = ctx if channel is None else ctx.guild.get_channel(channel)
|
|
38
|
+
|
|
39
|
+
if channel_to_delete is None:
|
|
40
|
+
raise Exception(f"Le channel {channel} n'existe pas !")
|
|
41
|
+
|
|
42
|
+
await sleep(timeout)
|
|
43
|
+
|
|
44
|
+
await channel_to_delete.delete(reason=reason)
|
|
45
|
+
|
|
46
|
+
return channel_to_delete.id
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def dshell_delete_channels(ctx: GuildChannel, name=None, regex=None, reason=None):
|
|
50
|
+
"""
|
|
51
|
+
Supprime tous les salons ayant le même nom et/ou le même regex.
|
|
52
|
+
Si aucun des deux n'est mis, il supprimera tous les salons comportant le même nom que celui ou a été fait la commande
|
|
53
|
+
"""
|
|
54
|
+
for channel in ctx.guild.channels:
|
|
55
|
+
|
|
56
|
+
if name is not None and channel.name == str(name):
|
|
57
|
+
await channel.delete(reason=reason)
|
|
58
|
+
|
|
59
|
+
elif regex is not None and search(regex, channel.name):
|
|
60
|
+
await channel.delete(reason=reason)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from discord import Embed
|
|
2
|
+
from discord.abc import GuildChannel
|
|
3
|
+
|
|
4
|
+
from .._DshellParser.ast_nodes import ListNode
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
'dshell_send_message',
|
|
8
|
+
'dshell_delete_message',
|
|
9
|
+
'dshell_purge_message'
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
async def dshell_send_message(ctx: GuildChannel, message=None, delete=None, channel=None, embeds: ListNode = None,
|
|
13
|
+
embed=None):
|
|
14
|
+
"""
|
|
15
|
+
Envoie un message sur Discord
|
|
16
|
+
"""
|
|
17
|
+
channel_to_send = ctx if channel is None else ctx.guild.get_channel(channel)
|
|
18
|
+
|
|
19
|
+
if channel_to_send is None:
|
|
20
|
+
raise Exception(f'Le channel {channel} est introuvable !')
|
|
21
|
+
|
|
22
|
+
if embeds is None:
|
|
23
|
+
embeds = ListNode([])
|
|
24
|
+
|
|
25
|
+
elif isinstance(embeds, Embed):
|
|
26
|
+
embeds = ListNode([embeds])
|
|
27
|
+
|
|
28
|
+
if embed is not None and isinstance(embed, Embed):
|
|
29
|
+
embeds.add(embed)
|
|
30
|
+
|
|
31
|
+
sended_message = await channel_to_send.send(message,
|
|
32
|
+
delete_after=delete,
|
|
33
|
+
embeds=embeds)
|
|
34
|
+
|
|
35
|
+
return sended_message.id
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def dshell_delete_message(ctx: GuildChannel, message, reason=None, delay=0):
|
|
39
|
+
"""
|
|
40
|
+
Supprime un message
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
delete_message = ctx.get_partial_message(message) # construit une référence au message (même s'il n'existe pas)
|
|
44
|
+
|
|
45
|
+
if delay > 3600:
|
|
46
|
+
raise Exception(f'Le délait de suppression du message est trop grand ! ({delay} secondes)')
|
|
47
|
+
|
|
48
|
+
await delete_message.delete(delay=delay, reason=reason)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def dshell_purge_message(ctx: GuildChannel, message_number, channel=None, reason=None):
|
|
52
|
+
"""
|
|
53
|
+
Purge les messages d'un salon
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
purge_channel = ctx if channel is None else ctx.guild.get_channel(channel)
|
|
57
|
+
|
|
58
|
+
if purge_channel is None:
|
|
59
|
+
raise Exception(f"Le salon {channel} à purgé est introuvable !")
|
|
60
|
+
|
|
61
|
+
await purge_channel.purge(limit=message_number, reason=reason)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .dshell_interpreter import *
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
from asyncio import sleep
|
|
2
|
+
from re import findall
|
|
3
|
+
from typing import TypeVar, Union, Any, Optional, Callable
|
|
4
|
+
|
|
5
|
+
from discord import AutoShardedBot, Embed, Colour
|
|
6
|
+
from discord.abc import GuildChannel, PrivateChannel
|
|
7
|
+
|
|
8
|
+
from .._DshellTokenizer.dshell_keywords import *
|
|
9
|
+
from .._DshellParser.ast_nodes import *
|
|
10
|
+
from .._DshellParser.dshell_parser import parse
|
|
11
|
+
from .._DshellParser.dshell_parser import to_postfix, print_ast
|
|
12
|
+
from .._DshellTokenizer.dshell_token_type import DshellTokenType as DTT
|
|
13
|
+
from .._DshellTokenizer.dshell_token_type import Token
|
|
14
|
+
from .._DshellTokenizer.dshell_tokenizer import DshellTokenizer
|
|
15
|
+
|
|
16
|
+
All_nodes = TypeVar('All_nodes', IfNode, LoopNode, ElseNode, ElifNode, ArgsCommandNode, VarNode, IdentOperationNode)
|
|
17
|
+
context = TypeVar('context', AutoShardedBot, GuildChannel, PrivateChannel)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DshellInterpreteur:
|
|
21
|
+
|
|
22
|
+
def __init__(self, ast_or_code: Union[list[All_nodes], str], ctx: context, auto=False, debug: bool = False):
|
|
23
|
+
"""
|
|
24
|
+
Interpreter Dshell code or AST.
|
|
25
|
+
"""
|
|
26
|
+
self.ast: StartNode = parse(DshellTokenizer(ast_or_code).start(), StartNode([]))[0][0]
|
|
27
|
+
self.env: dict[str, Any] = {}
|
|
28
|
+
self.ctx: context = ctx
|
|
29
|
+
if debug:
|
|
30
|
+
print_ast(self.ast)
|
|
31
|
+
|
|
32
|
+
async def execute(self, ast: Optional[list[All_nodes]] = None):
|
|
33
|
+
"""
|
|
34
|
+
Execute l'arbre syntaxique.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
for node in ast:
|
|
38
|
+
|
|
39
|
+
if isinstance(node, StartNode):
|
|
40
|
+
await self.execute(node.body)
|
|
41
|
+
|
|
42
|
+
if isinstance(node, CommandNode):
|
|
43
|
+
self.env['__cr__'] = await call_function(dshell_commands[node.name], node.body, self)
|
|
44
|
+
|
|
45
|
+
elif isinstance(node, IfNode):
|
|
46
|
+
elif_valid = False
|
|
47
|
+
if eval_expression(node.condition, self):
|
|
48
|
+
await self.execute(node.body)
|
|
49
|
+
return
|
|
50
|
+
elif node.elif_nodes:
|
|
51
|
+
|
|
52
|
+
for i in node.elif_nodes:
|
|
53
|
+
if eval_expression(i.condition, self):
|
|
54
|
+
await self.execute(i.body)
|
|
55
|
+
elif_valid = True
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
if not elif_valid and node.else_body is not None:
|
|
59
|
+
await self.execute(node.else_body.body)
|
|
60
|
+
|
|
61
|
+
elif isinstance(node, LoopNode):
|
|
62
|
+
self.env[node.variable.name.value] = 0
|
|
63
|
+
for i in DshellIterator(eval_expression(node.variable.body, self)):
|
|
64
|
+
self.env[node.variable.name.value] = i
|
|
65
|
+
await self.execute(node.body)
|
|
66
|
+
|
|
67
|
+
elif isinstance(node, VarNode):
|
|
68
|
+
|
|
69
|
+
first_node = node.body[0]
|
|
70
|
+
if isinstance(first_node, IfNode):
|
|
71
|
+
self.env[node.name.value] = eval_expression_inline(first_node, self)
|
|
72
|
+
|
|
73
|
+
elif isinstance(first_node, EmbedNode):
|
|
74
|
+
self.env[node.name.value] = build_embed(first_node.body, first_node.fields, self)
|
|
75
|
+
|
|
76
|
+
else:
|
|
77
|
+
self.env[node.name.value] = eval_expression(node.body, self)
|
|
78
|
+
|
|
79
|
+
elif isinstance(node, IdentOperationNode):
|
|
80
|
+
function = self.eval_data_token(node.function)
|
|
81
|
+
listNode = self.eval_data_token(node.ident)
|
|
82
|
+
if hasattr(listNode, function):
|
|
83
|
+
getattr(listNode, function)(self.eval_data_token(node.args))
|
|
84
|
+
|
|
85
|
+
elif isinstance(node, SleepNode):
|
|
86
|
+
sleep_time = eval_expression(node.body, self)
|
|
87
|
+
if sleep_time > 3600:
|
|
88
|
+
raise Exception(f'Le temps maximal de sommeil est de 3600 secondes !')
|
|
89
|
+
elif sleep_time < 1:
|
|
90
|
+
raise Exception(f'Le temps minimal de sommeil est de 1 seconde !')
|
|
91
|
+
|
|
92
|
+
await sleep(sleep_time)
|
|
93
|
+
|
|
94
|
+
elif isinstance(node, EndNode):
|
|
95
|
+
raise RuntimeError(f"Execution interromput -> #end atteint")
|
|
96
|
+
|
|
97
|
+
def eval_data_token(self, token: Token):
|
|
98
|
+
"""
|
|
99
|
+
Evalue les tokens de data
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
if not hasattr(token, 'type'):
|
|
103
|
+
return token
|
|
104
|
+
|
|
105
|
+
if token.type in (DTT.INT, DTT.MENTION):
|
|
106
|
+
return int(token.value)
|
|
107
|
+
elif token.type == DTT.FLOAT:
|
|
108
|
+
return float(token.value)
|
|
109
|
+
elif token.type == DTT.BOOL:
|
|
110
|
+
return token.value.lower() == "true"
|
|
111
|
+
elif token.type == DTT.LIST:
|
|
112
|
+
return ListNode(
|
|
113
|
+
[self.eval_data_token(tok) for tok in token.value]) # token.value contient déjà une liste de Token
|
|
114
|
+
elif token.type == DTT.IDENT:
|
|
115
|
+
if token.value in self.env.keys():
|
|
116
|
+
return self.env[token.value]
|
|
117
|
+
return token.value
|
|
118
|
+
elif token.type == DTT.CALL_ARGS:
|
|
119
|
+
return (self.eval_data_token(tok) for tok in token.value)
|
|
120
|
+
elif token.type == DTT.STR:
|
|
121
|
+
for match in findall(rf"\$({'|'.join(self.env.keys())})", token.value):
|
|
122
|
+
token.value = token.value.replace('$' + match, str(self.env[match]))
|
|
123
|
+
return token.value
|
|
124
|
+
else:
|
|
125
|
+
return token.value # fallback
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def eval_expression_inline(if_node: IfNode, interpreter: DshellInterpreteur) -> Token:
|
|
129
|
+
"""
|
|
130
|
+
Evalue une expression en ligne des variables
|
|
131
|
+
"""
|
|
132
|
+
if eval_expression(if_node.condition, interpreter):
|
|
133
|
+
return eval_expression(if_node.body, interpreter)
|
|
134
|
+
else:
|
|
135
|
+
return eval_expression(if_node.else_body.body, interpreter)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def eval_expression(tokens: list[Token], interpreter: DshellInterpreteur) -> Any:
|
|
139
|
+
"""
|
|
140
|
+
Evalue une expressions arithmétique et logique et renvoie son résultat. Cela peut-être un booléen, un entier, un flottant, une chaîne de caractère ou une liste
|
|
141
|
+
"""
|
|
142
|
+
postfix = to_postfix(tokens)
|
|
143
|
+
stack = []
|
|
144
|
+
|
|
145
|
+
for token in postfix:
|
|
146
|
+
|
|
147
|
+
if token.type in {DTT.INT, DTT.FLOAT, DTT.BOOL, DTT.STR, DTT.LIST, DTT.IDENT}:
|
|
148
|
+
stack.append(interpreter.eval_data_token(token))
|
|
149
|
+
|
|
150
|
+
elif token.type in (DTT.MATHS_OPERATOR, DTT.LOGIC_OPERATOR):
|
|
151
|
+
op = token.value
|
|
152
|
+
|
|
153
|
+
if op == "not":
|
|
154
|
+
a = stack.pop()
|
|
155
|
+
result = dshell_operators[op][0](a)
|
|
156
|
+
|
|
157
|
+
else:
|
|
158
|
+
b = stack.pop()
|
|
159
|
+
a = stack.pop()
|
|
160
|
+
result = dshell_operators[op][0](a, b)
|
|
161
|
+
|
|
162
|
+
stack.append(result)
|
|
163
|
+
|
|
164
|
+
else:
|
|
165
|
+
raise SyntaxError(f"Token inattendu en condition: {token}")
|
|
166
|
+
|
|
167
|
+
if len(stack) != 1:
|
|
168
|
+
raise SyntaxError("Condition mal formée")
|
|
169
|
+
|
|
170
|
+
return stack[0]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def call_function(function: Callable, args: ArgsCommandNode, interpreter: DshellInterpreteur):
|
|
174
|
+
"""
|
|
175
|
+
Appelle une fonction avec évaluation des arguments Dshell en valeurs Python
|
|
176
|
+
"""
|
|
177
|
+
reformatted = regroupe_commandes(args.body, interpreter)
|
|
178
|
+
|
|
179
|
+
# conversion des args en valeurs Python
|
|
180
|
+
absolute_args = reformatted.pop('*', list())
|
|
181
|
+
|
|
182
|
+
reformatted: dict[str, Token] # ne sert à rien, juste à indiquer ce qu'il contient dorénanvant
|
|
183
|
+
|
|
184
|
+
absolute_args.insert(0, interpreter.ctx)
|
|
185
|
+
keyword_args = {
|
|
186
|
+
key: value for key, value in reformatted.items()
|
|
187
|
+
}
|
|
188
|
+
return await function(*absolute_args, **keyword_args)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def regroupe_commandes(body: list[Token], interpreter: DshellInterpreteur) -> dict[Union[str, Token], list[Token]]:
|
|
192
|
+
"""
|
|
193
|
+
Regroupe les arguments de la commande sous la forme d'un dictionnaire python.
|
|
194
|
+
Sachant que l'on peut spécifier le paramètre que l'on souhaite passer via -- suivit du nom du paramètre. Mais ce n'est pas obligatoire !
|
|
195
|
+
Les paramètres non obligatoire seront stocké dans une liste sous la forme de tokens avec comme clé '*'.
|
|
196
|
+
Les autres ayant été spécifié via un séparateur, ils seront sous la forme d'une liste de tokens avec comme clé le token IDENT qui suivra le séparateur pour chaque argument.
|
|
197
|
+
"""
|
|
198
|
+
tokens = {'*': []} # les tokens à renvoyer
|
|
199
|
+
current_arg = '*' # les clés des arguments sont les types auquels ils appartiennent. L'* sert à tous les arguments non explicité par un séparateur et un IDENT
|
|
200
|
+
n = len(body)
|
|
201
|
+
|
|
202
|
+
i = 0
|
|
203
|
+
while i < n:
|
|
204
|
+
if body[i].type == DTT.SEPARATOR and body[
|
|
205
|
+
i + 1].type == DTT.IDENT: # On regarde si c'est un séparateur et si le token suivant est un IDENT
|
|
206
|
+
current_arg = body[i + 1].value # on change l'argument actuel. Il sera donc impossible de revenir à l'*
|
|
207
|
+
tokens[current_arg] = '' # on lui crée une paire clé/valeur
|
|
208
|
+
i += 2 # on skip l'IDENT qu'il y a après le séparateur car on vient de le traiter
|
|
209
|
+
else:
|
|
210
|
+
if current_arg == '*':
|
|
211
|
+
tokens[current_arg].append(interpreter.eval_data_token(body[i]))
|
|
212
|
+
else:
|
|
213
|
+
tokens[current_arg] = interpreter.eval_data_token(body[i]) # on ajoute le token à l'argument actuel
|
|
214
|
+
i += 1
|
|
215
|
+
return tokens
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def build_embed(body: list[Token], fields: list[FieldEmbedNode], interpreter: DshellInterpreteur) -> Embed:
|
|
219
|
+
"""
|
|
220
|
+
Construit un embed à partir des informations de la commande.
|
|
221
|
+
"""
|
|
222
|
+
args_main_embed: dict[Union[str, Token], list[Token]] = regroupe_commandes(body, interpreter)
|
|
223
|
+
args_main_embed.pop('*') # on enlève les paramètres non spécifié pour l'embed
|
|
224
|
+
args_main_embed: dict[str, Token] # on précise se qu'il contient dorénavant
|
|
225
|
+
|
|
226
|
+
args_fields: list[dict[str, Token]] = []
|
|
227
|
+
for field in fields: # on fait la même chose pour tous les fields
|
|
228
|
+
a = regroupe_commandes(field.body, interpreter)
|
|
229
|
+
a.pop('*')
|
|
230
|
+
a: dict[str, Token]
|
|
231
|
+
args_fields.append(a)
|
|
232
|
+
|
|
233
|
+
if 'color' in args_main_embed and isinstance(args_main_embed['color'],
|
|
234
|
+
ListNode): # si on passe l'argument de la couleur sous la forme d'une liste RGB
|
|
235
|
+
args_main_embed['color'] = Colour.from_rgb(*args_main_embed['color'])
|
|
236
|
+
|
|
237
|
+
embed = Embed(**args_main_embed) # on construit l'embed principal
|
|
238
|
+
for field in args_fields:
|
|
239
|
+
embed.add_field(**field) # on joute tous les fields
|
|
240
|
+
|
|
241
|
+
return embed
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class DshellIterator:
|
|
245
|
+
"""
|
|
246
|
+
Utilisé pour transformer n'importe quoi en un iterable
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
def __init__(self, data):
|
|
250
|
+
if isinstance(data, ListNode):
|
|
251
|
+
self.data = data
|
|
252
|
+
else:
|
|
253
|
+
self.data = data if isinstance(data, (str, list)) else range(int(data))
|
|
254
|
+
self.current = 0
|
|
255
|
+
|
|
256
|
+
def __iter__(self):
|
|
257
|
+
return self
|
|
258
|
+
|
|
259
|
+
def __next__(self):
|
|
260
|
+
|
|
261
|
+
if self.current >= len(self.data):
|
|
262
|
+
self.current = 0
|
|
263
|
+
raise StopIteration
|
|
264
|
+
|
|
265
|
+
value = self.data[self.current]
|
|
266
|
+
self.current += 1
|
|
267
|
+
return value
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from typing import Optional, Any
|
|
2
|
+
|
|
3
|
+
from .._DshellTokenizer.dshell_token_type import Token
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
'ASTNode',
|
|
7
|
+
'StartNode',
|
|
8
|
+
'ElseNode',
|
|
9
|
+
'ElifNode',
|
|
10
|
+
'IfNode',
|
|
11
|
+
'LoopNode',
|
|
12
|
+
'ArgsCommandNode',
|
|
13
|
+
'CommandNode',
|
|
14
|
+
'VarNode',
|
|
15
|
+
'EndNode',
|
|
16
|
+
'FieldEmbedNode',
|
|
17
|
+
'EmbedNode',
|
|
18
|
+
'SleepNode',
|
|
19
|
+
'IdentOperationNode',
|
|
20
|
+
'ListNode'
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
class ASTNode:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class StartNode(ASTNode):
|
|
28
|
+
def __init__(self, body: list):
|
|
29
|
+
self.body = body
|
|
30
|
+
|
|
31
|
+
def __repr__(self):
|
|
32
|
+
return f"<Command> - {self.body}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ElseNode(ASTNode):
|
|
36
|
+
def __init__(self, body: list[Token]):
|
|
37
|
+
self.body = body
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
return f"<Else> - {self.body}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ElifNode(ASTNode):
|
|
44
|
+
def __init__(self, condition: list[Token], body: list[Token], parent: "IfNode"):
|
|
45
|
+
self.condition = condition
|
|
46
|
+
self.body = body
|
|
47
|
+
self.parent = parent
|
|
48
|
+
|
|
49
|
+
def __repr__(self):
|
|
50
|
+
return f"<Elif> - {self.condition} - {self.body}"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class IfNode(ASTNode):
|
|
54
|
+
def __init__(self, condition: list[Token], body: list[Token], elif_nodes: Optional[list[ElifNode]] = None,
|
|
55
|
+
else_body: Optional[ElseNode] = None):
|
|
56
|
+
self.condition = condition
|
|
57
|
+
self.body = body
|
|
58
|
+
self.elif_nodes = elif_nodes
|
|
59
|
+
self.else_body = else_body
|
|
60
|
+
|
|
61
|
+
def __repr__(self):
|
|
62
|
+
return f"<If> - {self.condition} - {self.body} *- {self.elif_nodes} **- {self.else_body}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class LoopNode(ASTNode):
|
|
66
|
+
def __init__(self, variable: "VarNode", body: list):
|
|
67
|
+
self.variable = variable # content l'itérable dans son body
|
|
68
|
+
self.body = body
|
|
69
|
+
|
|
70
|
+
def __repr__(self):
|
|
71
|
+
return f"<Loop> - {self.variable.name} -> {self.variable.body} *- {self.body}"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ArgsCommandNode(ASTNode):
|
|
75
|
+
def __init__(self, body: list[Token]):
|
|
76
|
+
self.body: list[Token] = body
|
|
77
|
+
|
|
78
|
+
def __repr__(self):
|
|
79
|
+
return f"<Args Command> - {self.body}"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class CommandNode(ASTNode):
|
|
83
|
+
def __init__(self, name: str, body: ArgsCommandNode):
|
|
84
|
+
self.name = name
|
|
85
|
+
self.body = body
|
|
86
|
+
|
|
87
|
+
def __repr__(self):
|
|
88
|
+
return f"<{self.name}> - {self.body}"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class VarNode(ASTNode):
|
|
92
|
+
def __init__(self, name: Token, body: list[Token]):
|
|
93
|
+
self.name = name
|
|
94
|
+
self.body = body
|
|
95
|
+
|
|
96
|
+
def __repr__(self):
|
|
97
|
+
return f"<VAR> - {self.name} *- {self.body}"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class EndNode(ASTNode):
|
|
101
|
+
def __init__(self):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
def __repr__(self):
|
|
105
|
+
return f"<END>"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class FieldEmbedNode(ASTNode):
|
|
109
|
+
def __init__(self, body: list[Token]):
|
|
110
|
+
self.body: list[Token] = body
|
|
111
|
+
|
|
112
|
+
def __repr__(self):
|
|
113
|
+
return f"<EMBED_FIELD> - {self.body}"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class EmbedNode(ASTNode):
|
|
117
|
+
def __init__(self, body: list[Token], fields: list[FieldEmbedNode]):
|
|
118
|
+
self.body = body
|
|
119
|
+
self.fields = fields
|
|
120
|
+
|
|
121
|
+
def __repr__(self):
|
|
122
|
+
return f"<EMBED> - {self.body}"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class SleepNode(ASTNode):
|
|
126
|
+
def __init__(self, body: list[Token]):
|
|
127
|
+
self.body = body
|
|
128
|
+
|
|
129
|
+
def __repr__(self):
|
|
130
|
+
return f"<SLEEP> - {self.body}"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class IdentOperationNode(ASTNode):
|
|
134
|
+
"""
|
|
135
|
+
Gère les opération sur les idendificateur (appel de fonctions)
|
|
136
|
+
Faire en sorte que l'appel de la fonction renvoie la class associé pour permettre les imbrications. Pas obligatoire en soit si elle renvoie quelque chose
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def __init__(self, ident: Token, function: Token, args: Token):
|
|
140
|
+
self.ident = ident # content la "class"
|
|
141
|
+
self.function = function # contient la méthode appelé
|
|
142
|
+
self.args = args # contient une liste de tokens des arguments passé en paramètre
|
|
143
|
+
|
|
144
|
+
def __repr__(self):
|
|
145
|
+
return f"<IDENT OPERATION> - {self.ident}.{self.function}({self.args})"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class ListNode(ASTNode):
|
|
149
|
+
"""
|
|
150
|
+
Class iterable permettant de parcourir les listes créé à partir du code Dshell.
|
|
151
|
+
Cette class permet aussi d'intéragir avec la liste via des méthodes spécifique non built-in par python.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
def __init__(self, body: list[Any]):
|
|
155
|
+
self.iterable: list[Any] = body
|
|
156
|
+
self.len_iterable: int = len(body)
|
|
157
|
+
self.iterateur_count: int = 0
|
|
158
|
+
|
|
159
|
+
def add(self, value: Any):
|
|
160
|
+
"""
|
|
161
|
+
Ajoute un token à la liste
|
|
162
|
+
"""
|
|
163
|
+
if self.len_iterable > 10000:
|
|
164
|
+
raise PermissionError('Une liste ne peut dépasser les 10.000 éléments !')
|
|
165
|
+
|
|
166
|
+
self.iterable.append(value)
|
|
167
|
+
self.len_iterable += 1
|
|
168
|
+
|
|
169
|
+
def remove(self, value: Any, number: int = 1):
|
|
170
|
+
"""
|
|
171
|
+
Enlève un ou plusieurs token de la liste
|
|
172
|
+
"""
|
|
173
|
+
if number < 1:
|
|
174
|
+
raise Exception(f"Le nombre d'élément à retirer doit-être égale ou supperieur à 1 !")
|
|
175
|
+
|
|
176
|
+
def __add__(self, other: "ListNode"):
|
|
177
|
+
for i in other:
|
|
178
|
+
self.add(i)
|
|
179
|
+
return self
|
|
180
|
+
|
|
181
|
+
def __iter__(self):
|
|
182
|
+
return self
|
|
183
|
+
|
|
184
|
+
def __next__(self):
|
|
185
|
+
|
|
186
|
+
if self.iterateur_count >= self.len_iterable:
|
|
187
|
+
self.iterateur_count = 0
|
|
188
|
+
raise StopIteration()
|
|
189
|
+
|
|
190
|
+
v = self.iterable[self.iterateur_count]
|
|
191
|
+
self.iterateur_count += 1
|
|
192
|
+
return v
|
|
193
|
+
|
|
194
|
+
def __len__(self):
|
|
195
|
+
return self.len_iterable
|
|
196
|
+
|
|
197
|
+
def __getitem__(self, item):
|
|
198
|
+
return self.iterable[item]
|
|
199
|
+
|
|
200
|
+
def __bool__(self):
|
|
201
|
+
return bool(self.iterable)
|
|
202
|
+
|
|
203
|
+
def __repr__(self):
|
|
204
|
+
return f"<LIST> - {self.iterable}"
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"parse",
|
|
3
|
+
"parser_inline",
|
|
4
|
+
"to_postfix",
|
|
5
|
+
"parse_postfix_expression",
|
|
6
|
+
"print_ast"
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
from typing import Union, TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from .ast_nodes import (ASTNode,
|
|
12
|
+
CommandNode,
|
|
13
|
+
IfNode,
|
|
14
|
+
LoopNode,
|
|
15
|
+
ElseNode,
|
|
16
|
+
ArgsCommandNode,
|
|
17
|
+
ElifNode,
|
|
18
|
+
VarNode,
|
|
19
|
+
EndNode,
|
|
20
|
+
SleepNode,
|
|
21
|
+
IdentOperationNode,
|
|
22
|
+
EmbedNode,
|
|
23
|
+
FieldEmbedNode)
|
|
24
|
+
from .._DshellTokenizer.dshell_token_type import DshellTokenType as DTT
|
|
25
|
+
from .._DshellTokenizer.dshell_token_type import Token
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from .._DshellTokenizer import dshell_operators
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def parse(token_lines: list[list[Token]], start_node: ASTNode) -> tuple[list[ASTNode], int]:
|
|
32
|
+
"""
|
|
33
|
+
Commande de base pour parser les tokens
|
|
34
|
+
"""
|
|
35
|
+
pointeur = 0 # pointeur sur les listes de tokens pour savoir ou parser
|
|
36
|
+
blocks: list[Union[ASTNode, EndNode]] = [start_node] # liste d'imbrication des blocks pour gérer l'imbrication
|
|
37
|
+
len_token_lines = len(token_lines)
|
|
38
|
+
|
|
39
|
+
while pointeur < len_token_lines:
|
|
40
|
+
|
|
41
|
+
tokens_by_line = token_lines[pointeur] # on récupère la liste de token par rapport au pointeur
|
|
42
|
+
first_token_line = tokens_by_line[0] # on récupère le premier token de la ligne
|
|
43
|
+
last_block = blocks[-1]
|
|
44
|
+
|
|
45
|
+
if first_token_line.type == DTT.COMMAND: # si le token est une comande
|
|
46
|
+
body = tokens_by_line[1:] # on récupère ses arguments
|
|
47
|
+
last_block.body.append(CommandNode(first_token_line.value,
|
|
48
|
+
ArgsCommandNode(body))) # on ajoute la commande au body du dernier bloc
|
|
49
|
+
|
|
50
|
+
############################## DSHELL KEYWORDS ##############################
|
|
51
|
+
|
|
52
|
+
elif first_token_line.type == DTT.KEYWORD: # si c'est un mot clé
|
|
53
|
+
|
|
54
|
+
if first_token_line.value == 'if': # si c'est une condition
|
|
55
|
+
if_node = IfNode(condition=tokens_by_line[1:],
|
|
56
|
+
body=[]) # on crée la node avec les arguments de condition du if
|
|
57
|
+
last_block.body.append(if_node)
|
|
58
|
+
_, p = parse(token_lines[pointeur + 1:],
|
|
59
|
+
if_node) # on parse le reste du code avec la node if_node comme commancement du nouveau parsing
|
|
60
|
+
pointeur += p + 1 # essentielle pour ne pas parser les lignes déjà faite
|
|
61
|
+
|
|
62
|
+
elif first_token_line.value == '#if':
|
|
63
|
+
if not isinstance(last_block, (IfNode, ElseNode, ElifNode)):
|
|
64
|
+
raise SyntaxError(f'[#IF] Aucun bloc conditionnel ouvert ligne {first_token_line.position} !')
|
|
65
|
+
|
|
66
|
+
if isinstance(last_block, (ElifNode, ElseNode)):
|
|
67
|
+
|
|
68
|
+
while isinstance(last_block, (ElifNode, ElseNode)):
|
|
69
|
+
blocks.pop()
|
|
70
|
+
last_block = blocks[-1]
|
|
71
|
+
blocks.pop()
|
|
72
|
+
return blocks, pointeur
|
|
73
|
+
|
|
74
|
+
elif first_token_line.value == 'elif':
|
|
75
|
+
if not isinstance(last_block, (IfNode, ElifNode)):
|
|
76
|
+
raise SyntaxError(f'[ELIF] Aucun bloc conditionnel ouvert ligne {first_token_line.position} !')
|
|
77
|
+
elif_node = ElifNode(condition=tokens_by_line[1:], body=[],
|
|
78
|
+
parent=last_block if isinstance(last_block, IfNode) else last_block.parent)
|
|
79
|
+
|
|
80
|
+
if isinstance(last_block, ElifNode):
|
|
81
|
+
last_block.parent.elif_nodes.append(elif_node)
|
|
82
|
+
else:
|
|
83
|
+
if last_block.elif_nodes is None:
|
|
84
|
+
last_block.elif_nodes = [elif_node]
|
|
85
|
+
else:
|
|
86
|
+
last_block.elif_nodes.append(elif_node)
|
|
87
|
+
|
|
88
|
+
blocks.append(elif_node)
|
|
89
|
+
|
|
90
|
+
elif first_token_line.value == 'else':
|
|
91
|
+
if not isinstance(last_block, (IfNode, ElifNode)):
|
|
92
|
+
raise SyntaxError(f'[ELSE] Aucun bloc conditionnel ouvert ligne {first_token_line.position} !')
|
|
93
|
+
|
|
94
|
+
if isinstance(last_block, ElseNode) and last_block.else_body is not None:
|
|
95
|
+
raise SyntaxError(f'[ELSE] Déjà définit et n\'accepte pas les doublons dans un même if !')
|
|
96
|
+
|
|
97
|
+
else_node = ElseNode(body=[])
|
|
98
|
+
|
|
99
|
+
if isinstance(last_block, ElifNode): # si le dernier bloc est un elif
|
|
100
|
+
last_block.parent.else_body = else_node # on ajoute le bloc else à son parent (qui est le dernier if)
|
|
101
|
+
else:
|
|
102
|
+
last_block.else_body = else_node # une fois le parsing fini, on l'ajoute au dernier bloc
|
|
103
|
+
blocks.append(else_node)
|
|
104
|
+
|
|
105
|
+
elif first_token_line.value == 'loop':
|
|
106
|
+
loop_node = LoopNode(VarNode(tokens_by_line[1], to_postfix(tokens_by_line[2:])), body=[])
|
|
107
|
+
last_block.body.append(loop_node)
|
|
108
|
+
_, p = parse(token_lines[pointeur + 1:],
|
|
109
|
+
loop_node) # on parse tous ce qu'il y a après l'instruction loop
|
|
110
|
+
pointeur += p + 1
|
|
111
|
+
|
|
112
|
+
elif first_token_line.value == '#loop': # si rencontré
|
|
113
|
+
if not isinstance(last_block, LoopNode):
|
|
114
|
+
raise SyntaxError(f'[#LOOP] Aucune loop ouverte ligne {first_token_line.position} !')
|
|
115
|
+
blocks.pop()
|
|
116
|
+
return blocks, pointeur # on renvoie les informations parsé à la dernière loop ouverte
|
|
117
|
+
|
|
118
|
+
elif first_token_line.value == 'var':
|
|
119
|
+
var_node = VarNode(name=token_lines[pointeur][1], body=[])
|
|
120
|
+
last_block.body.append(var_node)
|
|
121
|
+
result, status = parser_inline(tokens_by_line[
|
|
122
|
+
2:]) # on fait en sorte de mettre les tokens de la ligne séparé par des retour à la ligne à chaque condition/else
|
|
123
|
+
if status:
|
|
124
|
+
parse(result, var_node) # on parse le tout dans la variable
|
|
125
|
+
else:
|
|
126
|
+
# var_node.body = parse(result, StartNode([]))[0][0].body
|
|
127
|
+
var_node.body = result[0]
|
|
128
|
+
|
|
129
|
+
elif first_token_line.value == 'sleep':
|
|
130
|
+
sleep_node = SleepNode(tokens_by_line[1:])
|
|
131
|
+
last_block.body.append(sleep_node)
|
|
132
|
+
|
|
133
|
+
elif first_token_line.value == '#end': # node pour arrêter le programme si elle est rencontré
|
|
134
|
+
end_node = EndNode()
|
|
135
|
+
last_block.body.append(end_node)
|
|
136
|
+
|
|
137
|
+
############################## DISCORD KEYWORDS ##############################
|
|
138
|
+
|
|
139
|
+
elif first_token_line.type == DTT.DISCORD_KEYWORD:
|
|
140
|
+
|
|
141
|
+
if first_token_line.value == 'embed':
|
|
142
|
+
embed_node = EmbedNode(body=[], fields=[])
|
|
143
|
+
var_node = VarNode(tokens_by_line[1], body=[embed_node])
|
|
144
|
+
last_block.body.append(var_node)
|
|
145
|
+
_, p = parse(token_lines[pointeur + 1:], embed_node)
|
|
146
|
+
pointeur += p + 1
|
|
147
|
+
|
|
148
|
+
elif first_token_line.value == '#embed':
|
|
149
|
+
if not isinstance(last_block, EmbedNode):
|
|
150
|
+
raise SyntaxError(f'[#EMBED] Aucun embed ouvert ligne {first_token_line.position} !')
|
|
151
|
+
blocks.pop()
|
|
152
|
+
return blocks, pointeur
|
|
153
|
+
|
|
154
|
+
elif first_token_line.value == 'field':
|
|
155
|
+
if not isinstance(last_block, EmbedNode):
|
|
156
|
+
raise SyntaxError(f'[FIELD] Aucun embed ouvert ligne {first_token_line.position} !')
|
|
157
|
+
last_block.fields.append(FieldEmbedNode(tokens_by_line[1:]))
|
|
158
|
+
|
|
159
|
+
############################## AUTRE ##############################
|
|
160
|
+
|
|
161
|
+
elif first_token_line.type == DTT.IDENT:
|
|
162
|
+
if len(tokens_by_line) == 1:
|
|
163
|
+
last_block.body.append(CommandNode(name='sm', body=ArgsCommandNode([first_token_line])))
|
|
164
|
+
|
|
165
|
+
else:
|
|
166
|
+
last_block.body += parse_postfix_expression(to_postfix(tokens_by_line))
|
|
167
|
+
|
|
168
|
+
elif first_token_line.type == DTT.STR:
|
|
169
|
+
last_block.body.append(CommandNode(name='sm', body=ArgsCommandNode([first_token_line])))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
else:
|
|
173
|
+
last_block.body += tokens_by_line
|
|
174
|
+
|
|
175
|
+
pointeur += 1
|
|
176
|
+
|
|
177
|
+
return blocks, pointeur
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
"""
|
|
181
|
+
elif first_token_line.type == DTT.LIST: # si le token est une liste (qui comporte une liste python avec des Tokens)
|
|
182
|
+
list_node = ListNode(first_token_line.value) # le .value est une liste python
|
|
183
|
+
last_block.body.append(list_node)
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def parser_inline(tokens: list[Token]) -> tuple[list[list[Token]], bool]:
|
|
188
|
+
"""
|
|
189
|
+
Transforme une ligne avec un if/else inline en structure multilignes
|
|
190
|
+
"""
|
|
191
|
+
result: list[list[Token]] = []
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
if_index = next(i for i, tok in enumerate(tokens) if tok.value == 'if')
|
|
195
|
+
else_index = next(i for i, tok in enumerate(tokens) if tok.value == 'else')
|
|
196
|
+
except StopIteration:
|
|
197
|
+
return [tokens], False # ligne normale
|
|
198
|
+
|
|
199
|
+
value_tokens = tokens[:if_index]
|
|
200
|
+
condition_tokens = tokens[if_index + 1:else_index]
|
|
201
|
+
else_tokens = tokens[else_index + 1:]
|
|
202
|
+
|
|
203
|
+
# On génère :
|
|
204
|
+
result.append([tokens[if_index]] + condition_tokens) # ligne "if cond"
|
|
205
|
+
result.append(value_tokens) # body du if
|
|
206
|
+
result.append([tokens[else_index]]) # ligne "else"
|
|
207
|
+
result.append(else_tokens) # body du else
|
|
208
|
+
return result, True
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def to_postfix(expression):
|
|
212
|
+
"""
|
|
213
|
+
Transforme l'expression en notation postfixée (RPN)
|
|
214
|
+
:param expression: l'expression donné par le tokenizer
|
|
215
|
+
:return: l'expression en notation postfixée
|
|
216
|
+
"""
|
|
217
|
+
output = []
|
|
218
|
+
operators: list[Token] = []
|
|
219
|
+
|
|
220
|
+
for token in expression:
|
|
221
|
+
if token.type in (DTT.IDENT, DTT.CALL_ARGS, DTT.INT, DTT.FLOAT, DTT.LIST, DTT.STR): # Si c'est un ident
|
|
222
|
+
output.append(token)
|
|
223
|
+
elif token.value in dshell_operators:
|
|
224
|
+
while (operators and operators[-1].value in dshell_operators and
|
|
225
|
+
dshell_operators[operators[-1].value][1] >= dshell_operators[token.value][1]):
|
|
226
|
+
output.append(operators.pop())
|
|
227
|
+
operators.append(token)
|
|
228
|
+
else:
|
|
229
|
+
raise ValueError(f"Token inconnu : {token}")
|
|
230
|
+
|
|
231
|
+
while operators:
|
|
232
|
+
output.append(operators.pop())
|
|
233
|
+
|
|
234
|
+
return output
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def parse_postfix_expression(postfix_tokens: list[Token]) -> list[IdentOperationNode]:
|
|
238
|
+
stack = []
|
|
239
|
+
|
|
240
|
+
for tok in postfix_tokens:
|
|
241
|
+
|
|
242
|
+
if tok.type in (DTT.IDENT, DTT.CALL_ARGS, DTT.INT, DTT.STR, DTT.LIST):
|
|
243
|
+
stack.append(tok)
|
|
244
|
+
|
|
245
|
+
elif tok.type == DTT.MATHS_OPERATOR:
|
|
246
|
+
if tok.value == '.':
|
|
247
|
+
args = stack.pop()
|
|
248
|
+
func = stack.pop()
|
|
249
|
+
base = stack.pop()
|
|
250
|
+
node = IdentOperationNode(ident=base, function=func, args=args)
|
|
251
|
+
stack.append(node)
|
|
252
|
+
|
|
253
|
+
elif tok.value == '->':
|
|
254
|
+
value = stack.pop()
|
|
255
|
+
base = stack.pop()
|
|
256
|
+
fake_func = Token(DTT.IDENT, 'at', tok.position)
|
|
257
|
+
fake_args = Token(DTT.CALL_ARGS, value.value, value.position)
|
|
258
|
+
node = IdentOperationNode(ident=base, function=fake_func, args=fake_args)
|
|
259
|
+
stack.append(node)
|
|
260
|
+
|
|
261
|
+
else:
|
|
262
|
+
raise SyntaxError(f"Opérateur non supporté dans les appels : {tok.value}")
|
|
263
|
+
|
|
264
|
+
else:
|
|
265
|
+
raise SyntaxError(f"Token inattendu : {tok}")
|
|
266
|
+
|
|
267
|
+
if len(stack) != 1:
|
|
268
|
+
raise SyntaxError("Expression mal formée ou incomplète")
|
|
269
|
+
|
|
270
|
+
return stack
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def print_ast(ast: ASTNode, decalage: int = 0):
|
|
274
|
+
for i in ast.body:
|
|
275
|
+
|
|
276
|
+
if isinstance(i, LoopNode):
|
|
277
|
+
print(f"{' ' * decalage}LOOP -> {i.variable.name} : {i.variable.body}")
|
|
278
|
+
print_ast(i, decalage + 5)
|
|
279
|
+
|
|
280
|
+
elif isinstance(i, IfNode):
|
|
281
|
+
print(f"{' ' * decalage}IF -> {i.condition}")
|
|
282
|
+
print_ast(i, decalage + 5)
|
|
283
|
+
|
|
284
|
+
if i.elif_nodes is not None:
|
|
285
|
+
for elif_body in i.elif_nodes:
|
|
286
|
+
print(f"{' ' * decalage}ELIF -> {elif_body.condition}")
|
|
287
|
+
print_ast(elif_body, decalage + 5)
|
|
288
|
+
|
|
289
|
+
if i.else_body is not None:
|
|
290
|
+
print(f"{' ' * decalage}ELSE -> ...")
|
|
291
|
+
print_ast(i.else_body, decalage + 5)
|
|
292
|
+
|
|
293
|
+
elif isinstance(i, IdentOperationNode):
|
|
294
|
+
print(f"{' ' * decalage}IDENT_OPERATION -> {i.ident}.{i.function}({i.args})")
|
|
295
|
+
|
|
296
|
+
elif isinstance(i, CommandNode):
|
|
297
|
+
print(f"{' ' * decalage}COMMAND -> {i.name} : {i.body}")
|
|
298
|
+
|
|
299
|
+
elif isinstance(i, VarNode):
|
|
300
|
+
print(f"{' ' * decalage}VAR -> {i.name} : {i.body}")
|
|
301
|
+
|
|
302
|
+
elif isinstance(i, EmbedNode):
|
|
303
|
+
print(f"{' ' * decalage}EMBED :")
|
|
304
|
+
print_ast(i.fields, decalage + 5)
|
|
305
|
+
|
|
306
|
+
elif isinstance(i, FieldEmbedNode):
|
|
307
|
+
for field in i.body:
|
|
308
|
+
print(f"{' ' * decalage}FIELD -> {field.value}")
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"dshell_keyword",
|
|
3
|
+
"dshell_discord_keyword",
|
|
4
|
+
"dshell_commands",
|
|
5
|
+
"dshell_mathematical_operators",
|
|
6
|
+
"dshell_logical_operators",
|
|
7
|
+
"dshell_operators"
|
|
8
|
+
]
|
|
9
|
+
from typing import Callable
|
|
10
|
+
from ..DISCORD_COMMANDS.dshell_channel import *
|
|
11
|
+
from ..DISCORD_COMMANDS.dshell_message import *
|
|
12
|
+
|
|
13
|
+
dshell_keyword: set[str] = {
|
|
14
|
+
'if', 'else', 'elif', 'loop', '#end', 'var', '#loop', '#if', 'sleep'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
dshell_discord_keyword: set[str] = {
|
|
18
|
+
'embed', '#embed', 'field'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
dshell_commands: dict[str, Callable] = {
|
|
22
|
+
"sm": dshell_send_message, # send message
|
|
23
|
+
"dm": dshell_delete_message,
|
|
24
|
+
"pm": dshell_purge_message,
|
|
25
|
+
"cc": dshell_create_text_channel, # create channel
|
|
26
|
+
"dc": dshell_delete_channel, # delete channel
|
|
27
|
+
"dcs": dshell_delete_channels,
|
|
28
|
+
"uc": dshell_send_message,
|
|
29
|
+
# update channel (aura toutes les modifications possible -> servira à ne faire qu'une commande pour modifier plusieurs chose sur le salon)
|
|
30
|
+
"rc": dshell_send_message # rename channel
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
dshell_mathematical_operators: dict[str, tuple[Callable, int]] = {
|
|
34
|
+
r"<": (lambda a, b: a < b, 4),
|
|
35
|
+
r"<=": (lambda a, b: a <= b, 4),
|
|
36
|
+
r"=<": (lambda a, b: a <= b, 4),
|
|
37
|
+
r"=": (lambda a, b: a == b, 4),
|
|
38
|
+
r"!=": (lambda a, b: a != b, 4),
|
|
39
|
+
r"=!": (lambda a, b: a != b, 4),
|
|
40
|
+
r">": (lambda a, b: a > b, 4),
|
|
41
|
+
r">=": (lambda a, b: a >= b, 4),
|
|
42
|
+
r"=>": (lambda a, b: a >= b, 4),
|
|
43
|
+
|
|
44
|
+
r".": (lambda a, b: a.b, 9),
|
|
45
|
+
r"->": (lambda a: a.at, 10), # équivalent à l'appel .at(key)
|
|
46
|
+
|
|
47
|
+
r"+": (lambda a, b: a + b, 6),
|
|
48
|
+
r"-": (lambda a, b=None: -a if b is None else a - b, 6),
|
|
49
|
+
# attention : ambiguïté entre unaire et binaire à traiter dans ton parseur
|
|
50
|
+
r"**": (lambda a, b: a ** b, 8),
|
|
51
|
+
r"*": (lambda a, b: a * b, 7),
|
|
52
|
+
r"%": (lambda a, b: a % b, 7),
|
|
53
|
+
r"//": (lambda a, b: a // b, 7),
|
|
54
|
+
r"/": (lambda a, b: a / b, 7),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
dshell_logical_operators: dict[str, tuple[Callable, int]] = {
|
|
58
|
+
|
|
59
|
+
r"and": (lambda a, b: bool(a and b), 2),
|
|
60
|
+
r"&": (lambda a, b: a & b, 2),
|
|
61
|
+
r"or": (lambda a, b: bool(a or b), 1),
|
|
62
|
+
r"|": (lambda a, b: a | b, 1),
|
|
63
|
+
r"in": (lambda a, b: a in b, 4),
|
|
64
|
+
r"not": (lambda a: not a, 3),
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
dshell_operators: dict[str, tuple[Callable, int]] = dshell_logical_operators.copy()
|
|
69
|
+
dshell_operators.update(dshell_mathematical_operators)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
'''
|
|
74
|
+
C_create_var = "var"
|
|
75
|
+
C_obligate_var = "ovar" # rend obligatoire les variables
|
|
76
|
+
|
|
77
|
+
# guild
|
|
78
|
+
C_create_channel = "cc"
|
|
79
|
+
C_create_voice_channel = "cvc"
|
|
80
|
+
C_create_forum_channel = "cfc"
|
|
81
|
+
C_create_category = "cca"
|
|
82
|
+
C_create_role = "cr"
|
|
83
|
+
|
|
84
|
+
C_delete_channel = "dc"
|
|
85
|
+
C_delete_category = "dca"
|
|
86
|
+
C_delete_role = "dr"
|
|
87
|
+
|
|
88
|
+
C_edit_channel = "ec"
|
|
89
|
+
C_edit_voice_channel = "evc"
|
|
90
|
+
C_edit_forum_channel = "efc"
|
|
91
|
+
C_edit_category = "eca"
|
|
92
|
+
C_edit_role = "er"
|
|
93
|
+
C_edit_guild = "eg"
|
|
94
|
+
|
|
95
|
+
# forum
|
|
96
|
+
C_edit_ForumTag = "eft"
|
|
97
|
+
C_create_thread = "ct"
|
|
98
|
+
C_delete_tread = "dt"
|
|
99
|
+
|
|
100
|
+
# member
|
|
101
|
+
C_edit_nickname = "en"
|
|
102
|
+
C_ban_member = "bm"
|
|
103
|
+
C_unban_member = "um"
|
|
104
|
+
C_kick_member = "km"
|
|
105
|
+
C_timeout_member = "tm"
|
|
106
|
+
C_move_member = "mm"
|
|
107
|
+
C_add_roles = "ar"
|
|
108
|
+
C_remove_roles = "rr"
|
|
109
|
+
|
|
110
|
+
# message
|
|
111
|
+
C_send_message = "sm"
|
|
112
|
+
C_respond_message = "rm"
|
|
113
|
+
C_edit_message = "em"
|
|
114
|
+
C_send_user_message = "sum"
|
|
115
|
+
C_delete_message = "dm"
|
|
116
|
+
C_purge_message = "pm"
|
|
117
|
+
C_create_embed = "e"
|
|
118
|
+
C_regex = "regex"
|
|
119
|
+
C_add_emoji = "ae"
|
|
120
|
+
C_remove_emoji = "re"
|
|
121
|
+
C_clear_emoji = "ce"
|
|
122
|
+
C_remove_reaction = "rre"
|
|
123
|
+
|
|
124
|
+
# bouton
|
|
125
|
+
C_create_button = "b"'''
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from enum import Enum, auto
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
'DshellTokenType',
|
|
6
|
+
'Token',
|
|
7
|
+
'MASK_CHARACTER'
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
MASK_CHARACTER = '§'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DshellTokenType(Enum):
|
|
14
|
+
INT = auto()
|
|
15
|
+
FLOAT = auto()
|
|
16
|
+
STR = auto()
|
|
17
|
+
BOOL = auto()
|
|
18
|
+
LIST = auto()
|
|
19
|
+
CALL_ARGS = auto()
|
|
20
|
+
DICT = auto()
|
|
21
|
+
MENTION = auto()
|
|
22
|
+
IDENT = auto() # nom de variable, fonction
|
|
23
|
+
KEYWORD = auto() # if, let, end, etc.
|
|
24
|
+
DISCORD_KEYWORD = auto() # embed, #embed...
|
|
25
|
+
COMMAND = auto()
|
|
26
|
+
SEPARATOR = auto()
|
|
27
|
+
MATHS_OPERATOR = auto() # ==, +, -, *, etc.
|
|
28
|
+
LOGIC_OPERATOR = auto(),
|
|
29
|
+
COMMENT = auto() # lignes commençant par ##
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Token:
|
|
33
|
+
def __init__(self, type_: "DTT", value: Union[str, list], position: int):
|
|
34
|
+
self.type = type_
|
|
35
|
+
self.value = value
|
|
36
|
+
self.position = position
|
|
37
|
+
|
|
38
|
+
def __repr__(self):
|
|
39
|
+
return f"<{self.type.name} '{self.value}'>"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"DshellTokenizer",
|
|
3
|
+
"table_regex",
|
|
4
|
+
"MASK_CHARACTER"
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
from re import DOTALL, IGNORECASE
|
|
8
|
+
from re import compile, Pattern, finditer, escape, sub, findall
|
|
9
|
+
|
|
10
|
+
from .dshell_keywords import *
|
|
11
|
+
from .dshell_token_type import DshellTokenType as DTT
|
|
12
|
+
from .dshell_token_type import Token
|
|
13
|
+
|
|
14
|
+
MASK_CHARACTER = '§'
|
|
15
|
+
|
|
16
|
+
table_regex: dict[DTT, Pattern] = {
|
|
17
|
+
DTT.COMMENT: compile(r"::(.*)"),
|
|
18
|
+
# DTT.DICT: compile(r"\{(.*?)\}"),
|
|
19
|
+
DTT.CALL_ARGS: compile(r"\((.*?)\)"),
|
|
20
|
+
DTT.STR: compile(r"\"(.*?)\"", flags=DOTALL),
|
|
21
|
+
DTT.LIST: compile(r"\[(.*?)\]"),
|
|
22
|
+
DTT.MENTION: compile(r'<(?:@!?|@&|#)([0-9]+)>'),
|
|
23
|
+
DTT.KEYWORD: compile(rf"(?<!\w)(#?{'|'.join(dshell_keyword)})(?!\w)"),
|
|
24
|
+
DTT.DISCORD_KEYWORD: compile(rf"(?<!\w|-)(#?{'|'.join(dshell_discord_keyword)})(?!\w|-)"),
|
|
25
|
+
DTT.SEPARATOR: compile(rf"(--)"),
|
|
26
|
+
DTT.COMMAND: compile(rf"\b({'|'.join(dshell_commands.keys())})\b"),
|
|
27
|
+
DTT.MATHS_OPERATOR: compile(rf"({'|'.join([escape(i) for i in dshell_mathematical_operators.keys()])})"),
|
|
28
|
+
DTT.LOGIC_OPERATOR: compile(rf"(?<!\w)({'|'.join([escape(i) for i in dshell_logical_operators.keys()])})(?<!\w)"),
|
|
29
|
+
DTT.FLOAT: compile(r"(\d+\.\d+)"),
|
|
30
|
+
DTT.INT: compile(r"(\d+)"),
|
|
31
|
+
DTT.BOOL: compile(r"(True|False)", flags=IGNORECASE),
|
|
32
|
+
DTT.IDENT: compile(rf"([A-Za-z0-9_]+)")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DshellTokenizer:
|
|
37
|
+
|
|
38
|
+
def __init__(self, code: str):
|
|
39
|
+
"""
|
|
40
|
+
Init le tokenizer.
|
|
41
|
+
:param code: Le code à tokenizer
|
|
42
|
+
"""
|
|
43
|
+
self.code: str = code
|
|
44
|
+
|
|
45
|
+
def start(self):
|
|
46
|
+
"""
|
|
47
|
+
Démarre le tokenizer pour qu'il traîte le code actuel.
|
|
48
|
+
Renvoie un tableau de tokens par ligne (séparé normalement pas des \n)
|
|
49
|
+
"""
|
|
50
|
+
splited_commandes = self.split(self.code)
|
|
51
|
+
return self.tokenizer(splited_commandes)
|
|
52
|
+
|
|
53
|
+
def tokenizer(self, commandes_lines: list[str]) -> list[list[Token]]:
|
|
54
|
+
"""
|
|
55
|
+
Tokenize chaque ligne de code
|
|
56
|
+
:param commandes_lines: Le code séparé en plusieurs lignes par la méthode split
|
|
57
|
+
"""
|
|
58
|
+
tokens: list[list[Token]] = []
|
|
59
|
+
|
|
60
|
+
for ligne in commandes_lines: # iter chaque ligne du code
|
|
61
|
+
tokens_par_ligne: list[Token] = []
|
|
62
|
+
|
|
63
|
+
for token_type, pattern in table_regex.items(): # iter la table de régex pour tous les tester sur la ligne
|
|
64
|
+
|
|
65
|
+
for match in finditer(pattern, ligne): # iter les résultat du match pour avoir leur position
|
|
66
|
+
if token_type != DTT.COMMENT: # si ce n'est pas un commentaire
|
|
67
|
+
token = Token(token_type, match.group(1), match.start()) # on enregistre son token
|
|
68
|
+
tokens_par_ligne.append(token)
|
|
69
|
+
|
|
70
|
+
len_match = len(match.group(0))
|
|
71
|
+
ligne = ligne[:match.start()] + (MASK_CHARACTER * len_match) + ligne[
|
|
72
|
+
match.end():] # remplace la match qui vient d'avoir lieu pour ne pas le rematch une seconde fois
|
|
73
|
+
|
|
74
|
+
if token_type in (
|
|
75
|
+
DTT.LIST,
|
|
76
|
+
DTT.CALL_ARGS): # si c'est un regrouppement de donnée, on tokenize ce qu'il contient
|
|
77
|
+
result = self.tokenizer([token.value])
|
|
78
|
+
token.value = result[0] if len(
|
|
79
|
+
result) > 0 else result # gère si la structure de donnée est vide ou non
|
|
80
|
+
|
|
81
|
+
tokens_par_ligne.sort(key=lambda
|
|
82
|
+
token: token.position) # trie la position par rapport aux positions de match des tokens pour les avoir dans l'ordre du code
|
|
83
|
+
if tokens_par_ligne:
|
|
84
|
+
tokens.append(tokens_par_ligne)
|
|
85
|
+
|
|
86
|
+
return tokens
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def split(commande: str, global_split='\n', garder_carractere_regroupant=True, carractere_regroupant='"') -> list[
|
|
90
|
+
str]:
|
|
91
|
+
"""
|
|
92
|
+
Sépare les commandes en une liste en respectant les chaînes entre guillemets.
|
|
93
|
+
:param commande: La chaîne de caractères à découper.
|
|
94
|
+
:param global_split: Le séparateur utilisé (par défaut '\n').
|
|
95
|
+
:param garder_carractere_regroupant: Si False, enlève les guillemets autour des chaînes.
|
|
96
|
+
:param caractere_regroupant: Le caractère utilisé pour regrouper une chaîne (par défaut '"').
|
|
97
|
+
:return: Une liste des commandes découpées avec les chaînes restaurées.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
commandes: str = commande.strip()
|
|
101
|
+
remplacement_temporaire = '[REMPLACER]'
|
|
102
|
+
entre_caractere_regroupant = findall(fr'({carractere_regroupant}.*?{carractere_regroupant})', commandes,
|
|
103
|
+
flags=DOTALL) # repère les parties entre guillemets et les save
|
|
104
|
+
|
|
105
|
+
# current_command_text = [i[1: -1] for i in
|
|
106
|
+
# entre_carractere_regroupant.copy()] # enregistre les parties entre guillemets pour cette commande
|
|
107
|
+
|
|
108
|
+
res = sub(fr'({carractere_regroupant}.*?{carractere_regroupant})', remplacement_temporaire, commandes,
|
|
109
|
+
flags=DOTALL) # remplace les parties entre guillemets
|
|
110
|
+
|
|
111
|
+
res = res.split(global_split) # split les commandes sans les guillemets
|
|
112
|
+
|
|
113
|
+
# remet les guillemets à leurs place
|
|
114
|
+
result = []
|
|
115
|
+
for i in res:
|
|
116
|
+
while remplacement_temporaire in i:
|
|
117
|
+
i = i.replace(remplacement_temporaire,
|
|
118
|
+
entre_caractere_regroupant[0][1: -1] if not garder_carractere_regroupant else
|
|
119
|
+
entre_caractere_regroupant[0], 1)
|
|
120
|
+
entre_caractere_regroupant.pop(0)
|
|
121
|
+
result.append(i)
|
|
122
|
+
return result
|
Dshell/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from ._DshellInterpreteur.dshell_interpreter import DshellInterpreteur
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dshellInterpreter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Discord bot interpreter for creating custom commands and automations.
|
|
5
|
+
Home-page: https://github.com/BOXERRMD/Dshell_Interpreter
|
|
6
|
+
Author: Chronos
|
|
7
|
+
Author-email: vagabonwalybi@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/BOXERRMD/Dshell_Interpreter/issues
|
|
10
|
+
Project-URL: Source, https://github.com/BOXERRMD/Dshell_Interpreter
|
|
11
|
+
Keywords: discord bot interpreter automation commands
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: py-cord==2.6.1
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: keywords
|
|
26
|
+
Dynamic: license
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: project-url
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
Dynamic: summary
|
|
32
|
+
|
|
33
|
+
# Dshell_Interpreter
|
|
34
|
+
Python interpreter for Discord.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Dshell/__init__.py,sha256=UPvXnewe_8FX9aoevMA78UN1k8AY-u8LTY3vEVxaDxw,72
|
|
2
|
+
Dshell/DISCORD_COMMANDS/__init__.py,sha256=s58iMazDXNPDLZ3Hvymh__C5w7lcgEvaYfX0-SHocRo,62
|
|
3
|
+
Dshell/DISCORD_COMMANDS/dshell_channel.py,sha256=IubzNzBtGEcbgkL_NJOWRY1oqE3Yb-ySDMUmoHKjGzM,2135
|
|
4
|
+
Dshell/DISCORD_COMMANDS/dshell_message.py,sha256=jgSGBeDDHVy8u9fX2WN6wzh7I9WqVkeySlpmbJsBZjs,1919
|
|
5
|
+
Dshell/_DshellInterpreteur/__init__.py,sha256=xy5-J-R3YmY99JF3NBHTRRLsComFxpjnCA5xacISctU,35
|
|
6
|
+
Dshell/_DshellInterpreteur/dshell_interpreter.py,sha256=bjeSkdLw6dJucNnf-bISDT_vHCOvQ_qRl_-9GJ1rbjM,10780
|
|
7
|
+
Dshell/_DshellParser/__init__.py,sha256=ONDfhZMvClqP_6tE8SLjp-cf3pXL-auQYnfYRrHZxC4,56
|
|
8
|
+
Dshell/_DshellParser/ast_nodes.py,sha256=ArTyuk7AykJ3xKiW656BOdZg92YEwbfAlzZF1YQeZoA,5483
|
|
9
|
+
Dshell/_DshellParser/dshell_parser.py,sha256=iSlrSwoLz_yL4nXHMnPvycRik9iJ-cRzkXZMHRZw5qo,13129
|
|
10
|
+
Dshell/_DshellTokenizer/__init__.py,sha256=LIQSRhDx2B9pmPx5ADMwwD0Xr9ybneVLhHH8qrJWw_s,172
|
|
11
|
+
Dshell/_DshellTokenizer/dshell_keywords.py,sha256=NFa4VdfX26X5lU1Ps05hZkZg5IyBDpG9WgT9L1oTckM,3617
|
|
12
|
+
Dshell/_DshellTokenizer/dshell_token_type.py,sha256=Gp-Vg2P96oTaKpOEKGHAvER7l98mbcuwmapLVJgmoCI,937
|
|
13
|
+
Dshell/_DshellTokenizer/dshell_tokenizer.py,sha256=9ycZhz2X2uvZynE0illgi5AhtNyR_c5o1KX-pwCP1xM,5692
|
|
14
|
+
dshellinterpreter-0.1.0.dist-info/licenses/LICENSE,sha256=lNgcw1_xb7QENAQi3uHGymaFtbs0RV-ihiCd7AoLQjA,1082
|
|
15
|
+
dshellinterpreter-0.1.0.dist-info/METADATA,sha256=hkfhSw9LZKtUrbxV9xF-tow1leliffWOpaz_ijj94To,1093
|
|
16
|
+
dshellinterpreter-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
17
|
+
dshellinterpreter-0.1.0.dist-info/top_level.txt,sha256=B4CMhtmchGwPQJLuqUy0GhRG-0cUGxKL4GqEbCiB_vE,7
|
|
18
|
+
dshellinterpreter-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 BOXER
|
|
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 @@
|
|
|
1
|
+
Dshell
|