humalis 1.0.4 → 1.2.0

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.
@@ -30,6 +30,23 @@ function motColle(ligne, debut, premier) {
30
30
  }
31
31
  return null;
32
32
  }
33
+ /**
34
+ * Le guillemet qui ferme un {code "…"} ou un {accent "…"} commencé à `debut` : le premier « "} »
35
+ * où les accolades du contenu sont équilibrées ({code "{accent "…"}"}), sinon le premier « "} »
36
+ * ({code "{"}). -1 s'il n'y en a pas.
37
+ */
38
+ function finDuMorceau(texte, debut) {
39
+ let profondeur = 0;
40
+ for (let j = debut; j < texte.length; j++) {
41
+ if (texte.startsWith('"}', j) && profondeur <= 0)
42
+ return j;
43
+ if (texte[j] === '{')
44
+ profondeur++;
45
+ if (texte[j] === '}')
46
+ profondeur--;
47
+ }
48
+ return texte.indexOf('"}', debut);
49
+ }
33
50
  function decouperChaine(valeur, ligne, colonne) {
34
51
  const parties = [];
35
52
  let tampon = '';
@@ -42,6 +59,13 @@ function decouperChaine(valeur, ligne, colonne) {
42
59
  }
43
60
  i++;
44
61
  let nomVariable = '';
62
+ // {code "…"} et {accent "…"} : le morceau va jusqu'au premier « "} ».
63
+ const morceau = /^(code|accent)\s*"/.exec(valeur.slice(i));
64
+ const finMorceau = morceau ? finDuMorceau(valeur, i + morceau[0].length) : -1;
65
+ if (finMorceau >= 0) {
66
+ nomVariable = valeur.slice(i, finMorceau + 1);
67
+ i = finMorceau + 1;
68
+ }
45
69
  while (i < valeur.length && valeur[i] !== '}') {
46
70
  nomVariable += valeur[i];
47
71
  i++;
@@ -50,7 +74,21 @@ function decouperChaine(valeur, ligne, colonne) {
50
74
  throw new ErreurLexicale('accolade non fermée dans la chaîne : il manque }', ligne, colonne);
51
75
  }
52
76
  i++;
53
- parties.push({ type: 'variable', valeur: nomVariable.trim() });
77
+ const accent = /^accent\b(.*)$/s.exec(nomVariable.trim());
78
+ if (accent) {
79
+ const contenu = /^\s*"([\s\S]*)"\s*$/.exec(accent[1]);
80
+ if (!contenu)
81
+ throw new ErreurLexicale('accent attend un texte entre guillemets : {accent "mot"}.', ligne, colonne);
82
+ parties.push({ type: 'accent', valeur: contenu[1] });
83
+ }
84
+ else if (/^code\s+\S/s.test(nomVariable.trim())) {
85
+ const contenu = /^code\s+"([\s\S]*)"\s*$/.exec(nomVariable.trim());
86
+ if (!contenu)
87
+ throw new ErreurLexicale('code attend un texte entre guillemets : {code "titre"}.', ligne, colonne);
88
+ parties.push({ type: 'code', valeur: contenu[1] });
89
+ }
90
+ else
91
+ parties.push({ type: 'variable', valeur: nomVariable.trim() });
54
92
  continue;
55
93
  }
56
94
  tampon += valeur[i];
@@ -68,10 +106,52 @@ export function analyser(source) {
68
106
  for (let numLigne = 0; numLigne < lignes.length; numLigne++) {
69
107
  const ligne = lignes[numLigne];
70
108
  let i = 0;
109
+ // « code humalis : », « code : », et l'animation : « code humalis avec animation frappe au defilement : ».
110
+ const codeBrut = /^( *)(code)(?: +(?!avec\b)([\p{L}][\p{L}0-9_]*))?( +avec +animation +frappe(?: +au +d[eé]filement)?)? *: *$/u.exec(ligne);
111
+ if (codeBrut) {
112
+ const contenu = [];
113
+ const indentation = codeBrut[1].length;
114
+ if (indentation % 4 !== 0)
115
+ throw new ErreurLexicale('indentation incohérente : un multiple de 4 est attendu', numLigne + 1, 1);
116
+ const niveau = indentation / 4;
117
+ const courant = pile[pile.length - 1];
118
+ if (niveau > courant + 1)
119
+ throw new ErreurLexicale('indentation incohérente : un seul niveau à la fois', numLigne + 1, 1);
120
+ if (niveau === courant + 1) {
121
+ pile.push(niveau);
122
+ tokens.push({ type: 'INDENT', valeur: '', ligne: numLigne + 1, colonne: 1 });
123
+ }
124
+ else
125
+ while (pile.length > 1 && pile[pile.length - 1] > niveau) {
126
+ pile.pop();
127
+ tokens.push({ type: 'DEDENT', valeur: '', ligne: numLigne + 1, colonne: 1 });
128
+ }
129
+ if (pile[pile.length - 1] !== niveau)
130
+ throw new ErreurLexicale('indentation incohérente : ce niveau ne correspond à aucun bloc ouvert', numLigne + 1, 1);
131
+ while (numLigne + 1 < lignes.length) {
132
+ const suivante = lignes[numLigne + 1];
133
+ const marge = suivante.length - suivante.trimStart().length;
134
+ if (suivante.trim() !== '' && marge <= indentation)
135
+ break;
136
+ contenu.push(lignes[++numLigne]);
137
+ }
138
+ while (contenu.length && contenu[contenu.length - 1].trim() === '')
139
+ contenu.pop();
140
+ const marge = Math.min(...contenu.filter((l) => l.trim() !== '').map((l) => l.length - l.trimStart().length));
141
+ const brut = contenu.map((l) => l.slice(Math.min(marge, l.length - l.trimStart().length))).join('\n');
142
+ const ligneDebut = numLigne - contenu.length + 1;
143
+ tokens.push({ type: 'MOT_CLE', valeur: 'code', ligne: ligneDebut, colonne: indentation + 1, reserve: false, original: 'code' }, ...(codeBrut[3] ? [{ type: 'IDENTIFIANT', valeur: codeBrut[3], ligne: ligneDebut, colonne: indentation + 6 }] : []), ...(codeBrut[4] ? codeBrut[4].trim().split(/ +/).map((mot) => ({ type: 'IDENTIFIANT', valeur: mot, ligne: ligneDebut, colonne: ligne.indexOf(codeBrut[4]) + 1 })) : []), { type: 'PONCTUATION', valeur: ':', ligne: ligneDebut, colonne: ligne.indexOf(':') + 1 }, { type: 'FIN_LIGNE', valeur: '', ligne: ligneDebut, colonne: ligne.length + 1 }, { type: 'CODE_BRUT', valeur: brut, ligne: ligneDebut + 1, colonne: 1 }, { type: 'FIN_LIGNE', valeur: '', ligne: numLigne + 1, colonne: 1 });
144
+ continue;
145
+ }
71
146
  // « style : » au début d'une ligne, hors de tout bloc : les lignes
72
147
  // indentées qui suivent sont du CSS, recopié tel quel. Il contient des
73
148
  // {, des : et des # qui ne doivent pas être lus comme du Humalis.
74
- if (pile.length === 1 && /^style\s*:\s*$/i.test(ligne)) {
149
+ if (/^style\s*:\s*$/i.test(ligne)) {
150
+ // Juste après un bloc de haut de fichier (« theme : »), ce bloc se ferme ici.
151
+ while (pile.length > 1) {
152
+ pile.pop();
153
+ tokens.push({ type: 'DEDENT', valeur: '', ligne: numLigne + 1, colonne: 1 });
154
+ }
75
155
  const debut = numLigne;
76
156
  const contenu = [];
77
157
  while (numLigne + 1 < lignes.length && (lignes[numLigne + 1].trim() === '' || /^\s/.test(lignes[numLigne + 1]))) {
@@ -141,7 +221,30 @@ export function analyser(source) {
141
221
  const debut = i;
142
222
  i++;
143
223
  let valeur = '';
144
- while (i < ligne.length && ligne[i] !== '"') {
224
+ let profondeur = 0;
225
+ while (i < ligne.length && (ligne[i] !== '"' || (profondeur > 0 && ligne.indexOf('}', i + 1) >= 0))) {
226
+ // {code "…"} et {accent "…"} : recopiés tels quels jusqu'au premier « "} »
227
+ // (accolades, guillemets et antislashs compris).
228
+ const morceau = /^\{(code|accent)\s*"/.exec(ligne.slice(i));
229
+ const fin = morceau ? finDuMorceau(ligne, i + morceau[0].length) : -1;
230
+ if (fin >= 0) {
231
+ valeur += ligne.slice(i, fin + 2);
232
+ i = fin + 2;
233
+ continue;
234
+ }
235
+ // \" écrit un guillemet dans le texte ; tout autre antislash reste tel quel
236
+ // (« \\ » aussi, en entier : un texte peut finir par lui, comme avant).
237
+ if (ligne[i] === '\\' && ligne[i + 1] === '\\') {
238
+ valeur += '\\\\';
239
+ i += 2;
240
+ continue;
241
+ }
242
+ if (ligne[i] === '\\' && ligne[i + 1] === '"')
243
+ i++;
244
+ if (ligne[i] === '{')
245
+ profondeur++;
246
+ if (ligne[i] === '}' && profondeur > 0)
247
+ profondeur--;
145
248
  valeur += ligne[i];
146
249
  i++;
147
250
  }