/**
* Regroupe des fonctions utilisées dans les sections python (hors basthon)
* @module legacy/outils/algo/algo_python
*/
// ça plante avec cette version
// import Sk from 'skulpt'
// => faut prendre la vieille qui existe dans docroot/externals (ou utiliser plutôt basthon)
// Il faut donc que la section déclare l’outil skulpt ou appelle notre fct init
import { j3pElement, j3pEmpty, j3pShowError } from 'src/legacy/core/functions'
import { loadJs } from 'sesajs/dom'
import { j3pBaseUrl } from 'src/lib/core/constantes'
let Sk = window.Sk
export async function init () {
try {
if (Sk) return
const basePath = j3pBaseUrl + 'externals/editeurs/'
await loadJs(basePath + 'skulpt/skulpt.min.js')
await loadJs(basePath + 'skulpt/skulpt-stdlib.js')
Sk = window.Sk
if (!window.ace) await loadJs(basePath + 'ace/ace.js')
} catch (error) {
j3pShowError(error, { message: 'Le chargement de l’interpréteur python a échoué', mustNotify: true })
}
}
function ensureSk () {
if (!Sk) {
if (!window.Sk) throw Error('Skulpt n’est pas chargé (il faut appeler init avant)')
Sk = window.Sk
}
}
function getCleanCode (code) {
// skulpt plante si on lui passe un caractère non ascii…
return code
// .replace(/\t/g, ' ')
// .replace(/ {4}/g, '\t')
.replace(/[áàâ]/g, 'a')
.replace(/ç/g, 'c')
.replace(/[éèëê]/g, 'e')
.replace(/[îï]/g, 'i')
.replace(/[ô]/g, 'o')
.replace(/[ûüù]/g, 'u')
.replace(/’/g, '\'')
// et on remplace tous les autres caractères non ascii (hors de la plage 32-126) par une espace
.replace(/[^\t\n !"#$%&'()*+,\-./0-9:;<=>?@A-Z[\\\]^_`a-z{|}~]/g, ' ')
}
function outf (text) {
const mypre = document.getElementById('output')
mypre.innerHTML = mypre.innerHTML + text
}
function builtinRead (x) {
ensureSk()
if (Sk.builtinFiles === undefined || Sk.builtinFiles.files[x] === undefined) {
throw Error('File not found: "' + x + '"')
}
return Sk.builtinFiles.files[x]
}
export function runPython (editor, laConsole, ajoutPrint) {
ensureSk()
// ajoutPrint est un booléen (optionnel) qui vaut true par défaut
// lorsqu’il vaut false, le programme n’ajoute pas de print à la validation pour être testé
if (typeof ajoutPrint !== 'boolean') ajoutPrint = true
effacerOutput()
let prog = editor.getValue()
if (prog.lastIndexOf('\n') !== prog.length - 1) {
// c’est que je n’ai pas de retour à la ligne à la fin du programme, donc il me faut l’ajouter
prog += '\n'
}
if (laConsole) {
const contenuConsole = laConsole.getValue()
if (contenuConsole.indexOf('print') === 0 || !ajoutPrint) {
prog += laConsole.getValue()
} else {
prog += 'print ' + laConsole.getValue()
}
}
prog = getCleanCode(prog)
const mypre = document.getElementById('output')
mypre.innerHTML = ''
Sk.canvas = 'mycanvas'
Sk.pre = 'output'
Sk.configure({ output: outf, read: builtinRead, python3: true })
try {
// eslint-disable-next-line no-eval
eval(Sk.importMainWithBody('<stdin>', false, prog))
} catch (pythonError) {
console.error('erreur python', pythonError)
Println(pythonError.toString())
try {
const lig = pythonError.lineno || pythonError.traceback[0].lineno
Println('erreur ligne ' + lig)
editor.gotoLine(lig)
} catch (error) {
console.error('erreur lors de l’affichage de l’erreur python', error)
}
}
}
export function runPythonCode (prog) {
ensureSk()
prog = getCleanCode(prog)
const mypre = document.getElementById('output')
mypre.innerHTML = ''
Sk.canvas = 'mycanvas'
Sk.pre = 'output'
Sk.configure({ output: outf, read: builtinRead, python3: true })
try {
// eslint-disable-next-line no-eval
eval(Sk.importMainWithBody('<stdin>', false, prog))
} catch (e) {
console.error('erreur python', e)
Println(e.toString())
}
}
// ---------------------------------------------------------------------------
// Rajouté pour J3P
// ---------------------------------------------------------------------------
// getText ne semble pas être utilisée, mais pourquoi est-elle là alors ???
// eslint-disable-next-line no-unused-vars
function getText () {
// return editor.getValue();
let code = document.getElementById('pre_editor').innerHTML
code = code.split('<BR>').join('\n')
code = code.split('<br>').join('\n')
code = code.split(' ').join('')
// confirm(code);
return code
}
// setText ne semble pas être utilisée, mais pourquoi est-elle là alors ???
// eslint-disable-next-line no-unused-vars
function setText (texte) {
// editor.setValue(texte, -1);
document.getElementById('pre_editor').innerHTML = texte
}
export function getTextAce (editor) {
return editor.getValue()
}
export function setTextAce (editor, texte) {
editor.setValue(texte, -1)
}
// Print ne semble pas être utilisée, mais pourquoi est-elle là alors ???
// eslint-disable-next-line no-unused-vars
function Print (texte) {
const txt = (typeof texte !== 'undefined') ? texte : ''
const mypre = document.getElementById('output')
mypre.innerHTML = mypre.innerHTML + txt
}
function Println (texte) {
const txt = (typeof texte !== 'undefined') ? texte : ''
const mypre = document.getElementById('output')
mypre.innerHTML = mypre.innerHTML + txt + '\n'
}
function effacerOutput () {
const mypre = document.getElementById('output')
mypre.innerHTML = ''
}
export function desactiverEditeur (editeur, nomDivEditor, codePython) {
// editeur est le nom de l’éditeur
// nomDivEditor est le nom de la div contenant l’éditeur
// codePython est le dernier code présent dans l’éditeur
try {
const divEditor = (typeof nomDivEditor === 'string') ? j3pElement(nomDivEditor) : nomDivEditor
divEditor.addEventListener('input', function () { setTextAce(editeur, codePython) })
divEditor.addEventListener('keyup', function (e) {
const keynum = (window.event) ? e.keyCode : e.which
if (keynum === 46 || keynum === 8) {
setTextAce(editeur, codePython)
}
})
} catch (error) {
console.error(error)
}
}
/**
* Vérifie que le code contient bien un : en fin de chaque ligne contenant les mots clés qui le nécessitent
* @param {string} algo
* @return {{numLigne: number, presents: boolean}}
*/
export function verifDeuxPoints (algo) {
// algo est l’algo au format python
// on vérifie qu'à la fin de chaque ligne comportant un mot clé on ait les deux-points
const motsCles = ['def', 'for', 'if', 'while', 'elif', 'else']
const lignes = algo.split('\n')
let deuxPointsPresents = true
let numLigne = -1
for (let i = lignes.length - 1; i >= 0; i--) {
let txt = lignes[i].replace(/\t/g, '')
for (let k = 0; k < motsCles.length; k++) {
if (txt.indexOf(motsCles[k]) === 0) {
// je vérifie qu'à la fin j’ai bien deux points (attention aux espaces)
txt = txt.replace(/\s*/g, '')
if (txt.charAt(txt.length - 1) !== ':') {
deuxPointsPresents = false
numLigne = i
}
}
}
}
return { presents: deuxPointsPresents, numLigne: numLigne + 1 }
}
/**
* Vérifie que les lignes débutant avec def|for|if|while|elif|else se terminent par : (et pas les autres)
* En cas de pb (presents est à false) numLigne donne le n° de la ligne qui pose pb
* @param {string} algo
* @return {{numLigne: number, presents: boolean}}
*/
export function verifDeuxPointsBis (algo) {
// algo est l’algo au format python
// on vérifie qu'à la fin de chaque ligne comportant un mot clé on ait les deux-points à la fin, et pas les autres
let numLigne
const deuxPointsPresents = algo.split('\n').every(function (line, lineIndex) {
numLigne = lineIndex + 1
// si on trouve un de ces mots clés faut : en fin de ligne
if (/^[\t ]*(def|for|if|while|elif|else)[\t ]/.test(line)) {
// y’a le mot clé, éventuellement précédé d’une espace|tabulation, et suivi d’une espace|tabulation
return /: *$/.test(line) // ok si y’a les deux points à la fin (on teste pas entre les deux)
} else {
// faut pas les 2 points
return !(/: *$/.test(line))
}
})
return { presents: deuxPointsPresents, numLigne }
}
/**
* Vérifie que les lignes débutant avec def|for|if|while|elif|else se terminent par : (et pas les autres)
* En cas de pb (presents est à false) numLigne donne le n° de la ligne qui pose pb
* @param {string} algo
* @return {{numLigne: number, presents: boolean}}
* /
function getFirstLineWithColonPb (algo) {
// algo est l’algo au format python
// on vérifie qu'à la fin de chaque ligne comportant un mot clé on ait les deux-points à la fin, et pas les autres
let numLigne = 0
// avec every la boucle s’arrête au 1er false retourné
algo.split('\n').every(function (line, lineIndex) {
// si on trouve un de ces mots clés faut : en fin de ligne
if (/^[\t ]*(def|for|if|while|elif|else)[\t ]/.test(line)) {
// y’a le mot clé, éventuellement précédé d’une espace|tabulation, et suivi d’une espace|tabulation, on regarde la fin
if (!(/:[\t ]*$/.test(line))) numLigne = lineIndex + 1
} else {
// faut pas les 2 points
if (!(/: *$/.test(line))) numLigne = lineIndex + 1
}
// si on a affecté numLigne ça renverra false et la boucle s’arrêtera là
return numLigne === 0
})
return numLigne
} /* */
/**
* Vérifie que les lignes contiennent def|for|if|while|elif|else ou bien se terminent par :
* @param {string[]} algo
* @return {boolean}
*/
export function deuxPointsPresents (algo) {
return algo.split('\n').every(function (line) {
// si on trouve un de ces mots on considère ça ok
if (/(def|for|if|while|elif|else)/.test(line)) return true
// @todo mettre une regex un peu plus stricte, une par instruction, par ex
// if (/^ *def [a-zA-Z0-9_-]+ *$/.test(line)) return true
return /: */.test(line)
})
}
// verifPython ne semble pas être utilisée, mais pourquoi est-elle là alors ???
// eslint-disable-next-line no-unused-vars
function verifPython (algoEleve, algoSecret, entrees) {
const outputElt = j3pElement('output')
// on ajoute les entrees à tester
let codeEntrees = ''
Object.keys(entrees).forEach(function (cle) {
codeEntrees += cle + ' = ' + entrees[cle] + '\n'
})
// on ote les saisies de l’algo eleve (on garde les lignes sans "input(")
const cleanedAlgoEleve = algoEleve.split('\n').filter(function (line) {
if (!line) return false
return !(/input\(/.test(line))
}).join('\n')
// idem pour l’algo secret
const cleanedAlgoSecret = algoSecret.split('\n').filter(function (line) {
if (!line) return false
return !(/input\(/.test(line))
}).join('\n')
// on exécute données + algoSecret
runPythonCode(codeEntrees + cleanedAlgoSecret)
const sortieSecret = outputElt.innerHTML
// on exécute données + algoEleve
runPythonCode(codeEntrees + cleanedAlgoEleve)
const sortieEleve = outputElt.innerHTML
// on s’arrête si le test élève sort une erreur
if (sortieEleve.includes('Error')) {
outputElt.innerHTML = 'Ton programme a échoué avec\n' + codeEntrees + '\n' + outputElt.innerHTML
return false
}
// on compare les 2 exécutions (on vire \n et <br> avant comparaison)
if (sortieSecret.replace(/(\n|<br>)/g, '') === sortieEleve.replace(/(\n|<br>)/g, '')) {
j3pEmpty(outputElt)
return true
}
// on explique l’échec
outputElt.innerHTML = 'Résultat faux pour ' + codeEntrees
outputElt.innerHTML += '\nta réponse : ' + sortieEleve
outputElt.innerHTML += '\nla bonne réponse : ' + sortieSecret
return false
}