Maison >développement back-end >Tutoriel Python >Refaire un DSL à moteur de règles
Il y a quelques années, j'ai réimplémenté un langage spécifique à un domaine (DSL) initialement conçu pour le moteur de règles au travail. La réimplémentation du jouet a été écrite en Javascript (à l'origine en Python) et publiée sur GitHub. Je ne m'attendais pas à ce qu'il fasse grand-chose, car il a été spécialement conçu pour un cas d'utilisation très spécifique que je ne devrais pas révéler.
Une photo un peu mignonne vomi par le copilote de bing
L'objectif principal de la conception était quelque chose qui puisse être facilement sérialisable. L'exhaustivité de Turing n'était pas un problème, car je n'en avais besoin que pour faire seulement 2 choses :
J'ai d'abord commencé par écrire une fonction anonyme en Python. Cependant, lorsque j'ai tenté de répartir le travail sur un ensemble de threads/processus, l'interprète s'est plaint du fait que les lambdas ne sont pas sérialisables. À l'époque, j'avais besoin de logique pour vivre en dehors du code principal, j'ai donc finalement créé le DSL à cet effet.
La première chose qui m'est venue à l'esprit était Lisp, car j'aime la façon dont le code ressemble un peu à des tableaux/listes. La similitude est une bonne chose, car j'ai déjà stocké la configuration dans YAML. Par conséquent, je n'ai pas à me soucier de créer une nouvelle façon de représenter la logique.
Enregistrer la langue sous forme de liste apporte un avantage alternatif, je n'ai pas eu besoin de créer un analyseur à partir de zéro, c'est-à-dire pas besoin de tokeniser/effectuer une analyse lexicale (lexer). En d’autres termes, l’auteur est le lexer. Tout ce que j'avais besoin de mettre en œuvre est de prendre la liste d'entrée, de trouver s'il s'agit d'un programme (nous l'appelons une règle) et de l'exécuter par rapport à un contexte.
const schema = ["condition.Equal", ["basic.Field", "foo"], ["basic.Field", "bar"]]; // returns a function that checks if context.foo === context.bar const rule = ruler.parse(rule) const context = {foo: "meow", bar: "woof"}; rule(context) // returns false
Tout a bien fonctionné et comme prévu. Puis, il y a quelques jours, je suis tombé sur un article pour implémenter un schéma en Python. J'ai probablement lu l'article auparavant, alors que je consacrais beaucoup de temps à apprendre Clojure. Cependant, cette fois-ci, j'ai décidé de réimplémenter la bibliothèque, de nouveau avec Python, à partir de zéro.
Donc, cette fois, j'avais besoin de tokeniser et d'effectuer le lexer par moi-même. Si je ne m'occupe que des chiffres, tout est facile, mais quand il s'agit de cordes, les choses deviennent plus compliquées. J'ai suivi un autre tutoriel et redécouvert le projet make-a-lisp. Finalement, j'ai abandonné et utilisé le lexer fourni par hy-lang.
Le lexer prend une expression s et renvoie une structure qui ressemble à un arbre de syntaxe abstraite. À partir de là, je construis mon analyseur en parcourant l'arbre, renvoyant la règle sous forme de fermeture qui prend un dictionnaire comme contexte.
const schema = ["condition.Equal", ["basic.Field", "foo"], ["basic.Field", "bar"]]; // returns a function that checks if context.foo === context.bar const rule = ruler.parse(rule) const context = {foo: "meow", bar: "woof"}; rule(context) // returns false
Il n’y a pas de réel avantage à la nouvelle implémentation, car j’ai quitté mon poste depuis quelques années. L'implémentation que j'ai laissée fonctionne probablement encore bien à ce jour (il vaut mieux qu'elle le soit après tant d'itérations qui y ont conduit). Cependant, j’apprends encore une chose ou deux tout au long de ce voyage. Si vous trouvez cela intéressant, n'hésitez pas à consulter le Javascript (où il attend le schéma de règles dans un tableau) ou la nouvelle version de Python (expression s).
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!