ソースを参照

Refactor ivprogProcessor to use the new compatibility table

-Implement the processorErrorFactory and the semantic and runtime error classes
-Fix some issues with strings being printed with "
-Refactor the types and operators
Lucas de Souza 6 年 前
コミット
06082028a8

+ 22 - 1
i18n/pt/error.json

@@ -10,5 +10,26 @@
   "invalid_var_declaration": "Erro na linha $0. Variáveis só podem ser declarados no corpo principal da função e de preferência nas primeiras linhas.",
   "invalid_break_command": "Erro na linha $0. O comando $1 não pode ser usado fora de uma estrutura de repetição ou 'escolha...caso'",
   "invalid_terminal": "Não é possível utilizar $0 na expressão da linha: $1, coluna: $2. Tente um valor númerico, variável ou chamada de função.",
-  "id_missing": "Esperava-se um identificador, mas encontrou-se $0 na linha: $1, coluna: $2"
+  "id_missing": "Esperava-se um identificador, mas encontrou-se $0 na linha: $1, coluna: $2",
+  "main_missing": "A função principal não foi encontrada",
+  "invalid_global_var": "Erro crítico: Chamada inválida da função initGlobal fora do contexto BASE",
+  "not_implemented": "Erro interno crítico: A função definida pelo sistema $0 não foi implementada.",
+  "function_missing": "A função $0 não foi encontrada",
+  "invalid_parameters_size": "A quantidade de parâmetros fornecidos está incorreta. Esperava-se $0, encontrou-se $1",
+  "invalid_ref": "Você deve fornecer apenas um identificador como parâmetro",
+  "invalid_parameter_type": "O valor fornecido como parâmetro é do tipo $0, mas o tipo esperado é $1",
+  "unknown_command": "Erro interno crítico: Comando desconhecido encontrado",
+  "loop_condition_type": "A condição dos laços de repetição deve ser do tipo lógico",
+  "if_condition_type": "A condição de um comando se...senao deve ser do tipo lógico",
+  "invalid_return_type": "A função $0 deve retornar um tipo $1, ao invés de $1",
+  "unexpected_break_command": "Erro interno crítico: Comando pare encontrado fora do contexto de um laço/escolha..caso",
+  "invalid_dimension": "As dimensões de um vetor/matriz devem ser do tipo inteiro",
+  "void_in_expression": "A função $0 não pode ser utilizada aqui pois seu tipo de retorno é vazio",
+  "invalid_array": "Expressão literal de Vetor/Mariz inválida",
+  "invalid_array_access": "Identificador $0 não se refere a um vetor/matriz válido",
+  "column_outbounds": "Número de colunas $0 é inválido para a matriz $1 que possui $2 colunas",
+  "line_outbounds": "Número de linhas $0 é invaálido para a matriz $1 que possui $2 linhas",
+  "invalid_infix_op": "Não é possível aplicar a operação $0 entre os tipos $1 e $2",
+  "invalid_unary_op": "Não é possível aplicar a operação $0 ao tipo $1",
+  "unknown_op": "Erro interno crítico: Operação $0 desconhecida"
 }

js/ast/error/SyntaxError.js → js/ast/error/syntaxError.js


+ 1 - 1
js/ast/error/syntaxErrorFactory.js

@@ -1,5 +1,5 @@
 import { LocalizedStrings } from './../../services/localizedStringsService';
-import { SyntaxError } from './SyntaxError';
+import { SyntaxError } from './syntaxError';
 
 export const SyntaxErrorFactory = Object.freeze({
   extra_lines: () => new SyntaxError(LocalizedStrings.getError("extra_lines")),

+ 14 - 14
js/ast/operators.js

@@ -1,18 +1,18 @@
 export const Operators = Object.freeze({
-  ADD: 0,
-  SUB: 1,
-  MULT: 2,
-  DIV: 3,
-  MOD: 4,
-  GT: 5,
-  GE: 6,
-  LT: 7,
-  LE: 8,
-  EQ: 9,
-  NEQ: 10,
-  AND: 11,
-  OR: 12,
-  NOT: 13
+  ADD: {ord: 0, value: "+"},
+  SUB: {ord: 1, value: "-"},
+  MULT: {ord: 2, value: '*'},
+  DIV: {ord: 3, value: '/'},
+  MOD: {ord: 4, value: '%'},
+  GT: {ord: 5, value: '>'},
+  GE: {ord: 6, value: '>='},
+  LT: {ord: 7, value: '<'},
+  LE: {ord: 8, value: '<='},
+  EQ: {ord: 9, value: '=='},
+  NEQ: {ord: 10, value: '!='},
+  AND: {ord: 11, value: 'and'},
+  OR: {ord: 12, value: 'or'},
+  NOT: {ord: 13, value: 'not'}
 });
 
 export function convertFromString (op) {

+ 17 - 15
js/ast/types.js

@@ -1,12 +1,12 @@
 export const Types = Object.freeze({
-  INTEGER: "int",
-  REAL: "real",
-  STRING: "string",
-  BOOLEAN: "bool",
-  VOID: "void",
-  ARRAY: 'array',
-  UNDEFINED: 'undefined',
-  ALL: 'all'
+  INTEGER: {value: "int", ord: 0},
+  REAL: {value: "real", ord: 1},
+  STRING: {value: "string", ord: 2},
+  BOOLEAN: {value: "bool", ord: 3},
+  VOID: {value: "void", ord: 4},
+  ARRAY: {value: 'array', ord: 5},
+  UNDEFINED: {value: 'undefined', ord: 6},
+  ALL: {value: 'all', ord: 7}
 });
 
 export function toInt (str) {
@@ -20,13 +20,15 @@ export function toInt (str) {
 }
 
 export function toString (str) {
-  let value = str.replace("\\b", "\b");
-  value = value.replace("\\t", "\t");
-  value = value.replace("\\n", "\n");
-  value = value.replace("\\r", "\r");
-  value = value.replace("\\\"", "\"");
-  value = value.replace("\\\'", "\'");
-  value = value.replace("\\\\", "\\");
+  let value = str.replace(/^"/, '');
+  value = value.replace(/"$/, '');
+  value = value.replace(/\\b/g, "\b");
+  value = value.replace(/\\t/g, "\t");
+  value = value.replace(/\\n/g, "\n");
+  value = value.replace(/\\r/g, "\r");
+  value = value.replace(/\\\"/g, "\"");
+  value = value.replace(/\\\'/g, "\'");
+  value = value.replace(/\\\\/g, "\\");
   return value;
 }
 

+ 3 - 1
js/io/domOutput.js

@@ -9,8 +9,10 @@ export class DOMOutput extends Output {
   }
 
   sendOutput (text) {
-    text = text.replace(/"/g,'');
+    text = text.replace(/^"/, '');
+    text = text.replace(/"$/, '');
     text = text.replace("\n", '</br>');
+    text = text.replace(/\t/g,'&#9;');
     const span = $('<span />').addClass('ivprog-io-output-text').html(text);
     this.el.append(span);
   }

+ 3 - 3
js/main.js

@@ -19,9 +19,9 @@ console.log(LocalizedStrings.getUI('start'));
 //     i++;
 // }
 // const anaSin = new IVProgParser(input, ivprogLexer);
-//const editor = new JsonEditor('#json-renderer', {});
-//const domIn = new DOMInput('#dom-in');
-//const domOut = new DOMOutput('#dom-out');
+const editor = new JsonEditor('#json-renderer', {});
+const domIn = new DOMInput('#dom-in');
+const domOut = new DOMOutput('#dom-out');
 // proc.interpretAST().then( sto => {
 //   console.log(sto.applyStore('a'));
 // }).catch(e => console.log(e));

+ 150 - 52
js/processor/compatibilityTable.js

@@ -1,60 +1,158 @@
 import { Types } from './../ast/types';
 import { Operators } from './../ast/operators';
 
-const InfixCompatibilityTable = Object.freeze([
-  /*Add*/[Types.INTEGER, Types.REAL, Types.STRING],
-  /*Sub*/[Types.INTEGER, Types.REAL],
-  /*Mult*/[Types.INTEGER, Types.REAL],
-  /*Div*/[Types.INTEGER, Types.REAL],
-  /*Mod*/[Types.INTEGER],
-  /*Gt*/[Types.INTEGER, Types.REAL],
-  /*Ge*/[Types.INTEGER, Types.REAL],
-  /*Lt*/[Types.INTEGER, Types.REAL],
-  /*Le*/[Types.INTEGER, Types.REAL],
-  /*Eq*/[Types.INTEGER, Types.REAL, Types.STRING, Types.BOOLEAN],
-  /*Neq*/[Types.INTEGER, Types.REAL, Types.STRING, Types.BOOLEAN],
-  /*And*/[Types.BOOLEAN],
-  /*Or*/[Types.BOOLEAN],
-  /*Not*/null,
-]);
-
-const UnaryCompatibilityTable = Object.freeze([
-  /*Add*/[Types.INTEGER, Types.REAL],
-  /*Sub*/[Types.INTEGER, Types.REAL],
-  /*Mult*/null,
-  /*Div*/null,
-  /*Mod*/null,
-  /*Gt*/null,
-  /*Ge*/null,
-  /*Lt*/null,
-  /*Le*/null,
-  /*Eq*/null,
-  /*Neq*/null,
-  /*And*/null,
-  /*Or*/null,
-  /*Not*/[Types.BOOLEAN],
-]);
-
-export function canApplyUnaryOp (op, a) {
-  const list = UnaryCompatibilityTable[op];
-  if (!!!list) {
-    return false;
-  }
-  const type = list.find(t => t === a.type);
-  if (!!!type) {
-    return false;
-  }
-  return true
+function buildInfixAddTable () {
+  const table = [[], [], [], []];
+
+  table[Types.INTEGER.ord][Types.INTEGER.ord] = Types.INTEGER;
+  table[Types.INTEGER.ord][Types.REAL.ord] = Types.REAL;
+  table[Types.INTEGER.ord][Types.STRING.ord] = Types.STRING;
+
+  table[Types.REAL.ord][Types.INTEGER.ord] = Types.REAL;
+  table[Types.REAL.ord][Types.REAL.ord] = Types.REAL;
+  table[Types.REAL.ord][Types.STRING.ord] = Types.STRING;
+
+  table[Types.STRING.ord][Types.INTEGER.ord] = Types.STRING;
+  table[Types.STRING.ord][Types.REAL.ord] = Types.STRING;
+  table[Types.STRING.ord][Types.STRING.ord] = Types.STRING;
+  table[Types.STRING.ord][Types.BOOLEAN.ord] = Types.STRING;
+
+  return table;
+}
+
+function buildInfixMultiSubTable () {
+  const table = [[], [], [], []];
+
+  table[Types.INTEGER.ord][Types.INTEGER.ord] = Types.INTEGER;
+  table[Types.INTEGER.ord][Types.REAL.ord] = Types.REAL;
+
+  table[Types.REAL.ord][Types.INTEGER.ord] = Types.REAL;
+  table[Types.REAL.ord][Types.REAL.ord] = Types.REAL;
+
+  return table;
 }
 
-export function canApplyInfixOp (op, a, b) {
-  const list = InfixCompatibilityTable[op];
-  if (!!!list) {
-    return false;
+function buildInfixDivTable () {
+  const table = [[], [], [], []];
+
+  table[Types.INTEGER.ord][Types.INTEGER.ord] = Types.REAL;
+  table[Types.INTEGER.ord][Types.REAL.ord] = Types.REAL;
+
+  table[Types.REAL.ord][Types.INTEGER.ord] = Types.REAL;
+  table[Types.REAL.ord][Types.REAL.ord] = Types.REAL;
+
+  return table;
+}
+
+function buildInfixEqualityInequalityTable () {
+  const table = [[], [], [], []];
+
+  table[Types.INTEGER.ord][Types.INTEGER.ord] = Types.BOOLEAN;
+
+  table[Types.REAL.ord][Types.REAL.ord] = Types.BOOLEAN;
+
+  table[Types.BOOLEAN.ord][Types.BOOLEAN.ord] = Types.BOOLEAN;
+
+  table[Types.STRING.ord][Types.STRING.ord] = Types.BOOLEAN;
+
+  return table;
+}
+
+function buildInfixRelationalTable () {
+  const table = [[], [], [], []];
+
+  table[Types.INTEGER.ord][Types.INTEGER.ord] = Types.BOOLEAN;
+
+  table[Types.REAL.ord][Types.REAL.ord] = Types.BOOLEAN;
+
+  table[Types.STRING.ord][Types.STRING.ord] = Types.BOOLEAN;
+
+  return table;
+}
+
+function buildInfixAndOrTable () {
+  const table = [[], [], [], []];
+
+  table[Types.BOOLEAN.ord][Types.BOOLEAN.ord] = Types.BOOLEAN;
+
+  return table;
+}
+
+function buildInfixModTable () {
+  const table = [[], [], [], []];
+
+  table[Types.INTEGER.ord][Types.INTEGER.ord] = Types.INTEGER;
+
+  return table;
+}
+
+function buildUnarySumSubList () {
+  const list = [];
+
+  list[Types.INTEGER.ord] = Types.INTEGER;
+
+  list[Types.REAL.ord] = Types.REAL;
+
+  return list;
+}
+
+function buildUnaryNegList () {
+  const list = [];
+
+  list[Types.BOOLEAN.ord] = Types.BOOLEAN;
+
+  return list;
+}
+
+function buildInfixCompatibilityTable () {
+  const compatibilityMap = new WeakMap();
+  compatibilityMap.set(Operators.ADD, buildInfixAddTable());
+  compatibilityMap.set(Operators.SUB, buildInfixMultiSubTable());
+  compatibilityMap.set(Operators.MULT, buildInfixMultiSubTable());
+  compatibilityMap.set(Operators.DIV, buildInfixDivTable());
+  compatibilityMap.set(Operators.EQ, buildInfixEqualityInequalityTable());
+  compatibilityMap.set(Operators.NEQ, buildInfixEqualityInequalityTable());
+  compatibilityMap.set(Operators.GE, buildInfixRelationalTable());
+  compatibilityMap.set(Operators.GT, buildInfixRelationalTable());
+  compatibilityMap.set(Operators.LE, buildInfixRelationalTable());
+  compatibilityMap.set(Operators.LT, buildInfixRelationalTable());
+  compatibilityMap.set(Operators.OR, buildInfixAndOrTable());
+  compatibilityMap.set(Operators.AND, buildInfixAndOrTable());
+  compatibilityMap.set(Operators.MOD, buildInfixModTable());
+  return compatibilityMap;
+}
+
+function buildUnaryCompatibilityTable () {
+  const compatibilityMap = new WeakMap();
+  compatibilityMap.set(Operators.ADD, buildUnarySumSubList());
+  compatibilityMap.set(Operators.SUB, buildUnarySumSubList());
+  compatibilityMap.set(Operators.NOT, buildUnaryNegList());
+  return compatibilityMap;
+}
+
+const infixMap = buildInfixCompatibilityTable();
+const unaryMap = buildUnaryCompatibilityTable();
+
+export function resultTypeAfterInfixOp (operator, leftExpressionType, rightExpressionType) {
+  try {
+    return infixMap.get(operator)[leftExpressionType.ord][rightExpressionType.ord];
+  } catch (e) {
+    if (e instanceof TypeError) {
+      return Types.UNDEFINED;
+    } else {
+      throw e;
+    }
   }
-  const type = list.find(t => t === a.type);
-  if (!!!type) {
-    return false;
+}
+
+export function resultTypeAfterUnaryOp (operator, leftExpressionType) {
+  try {
+    return unaryMap.get(operator)[leftExpressionType.ord];
+  } catch (e) {
+    if (e instanceof TypeError) {
+      return Types.UNDEFINED;
+    } else {
+      throw e;
+    } 
   }
-  return type === b.type;
 }

+ 7 - 0
js/processor/error/processorErrorFactory.js

@@ -0,0 +1,7 @@
+import { RuntimeError } from './runtimeError';
+import { SemanticError } from './semanticError';
+import { LocalizedStrings } from './../../services/localizedStringsService';
+
+export const ProcessorErrorFactory  = Object.freeze({
+  
+});

+ 8 - 0
js/processor/error/runtimeError.js

@@ -0,0 +1,8 @@
+export class RuntimeError extends Error {
+
+  constructor (...msg) {
+    super(...msg);
+    if(Error.captureStackTrace)
+      Error.captureStackTrace(this, RuntimeError);
+  }
+}

+ 8 - 0
js/processor/error/semanticError.js

@@ -0,0 +1,8 @@
+export class SemanticError extends Error {
+
+  constructor (...msg) {
+    super(...msg);
+    if(Error.captureStackTrace)
+      Error.captureStackTrace(this, SemanticError);
+  }
+}

+ 56 - 56
js/processor/ivprogProcessor.js

@@ -7,7 +7,7 @@ import { Context } from './context';
 import { Types, toInt } from './../ast/types';
 import { Operators } from './../ast/operators';
 import { NAMES, LanguageDefinedFunction } from './definedFunctions';
-import { canApplyInfixOp, canApplyUnaryOp } from './compatibilityTable';
+import { resultTypeAfterInfixOp, resultTypeAfterUnaryOp } from './compatibilityTable';
 import * as Commands from './../ast/commands/';
 import * as Expressions from './../ast/expressions/';
 
@@ -170,8 +170,6 @@ export class IVProgProcessor {
 
               if(formalParameter.byRef) {
                 const ref = new StoreObjectRef(stoObj.id, callerStore);
-                console.log('it\'s a ref...');
-                console.log(ref);
                 calleeStore.insertStore(formalParameter.id, ref);
               } else {
                 calleeStore.insertStore(formalParameter.id, stoObj);
@@ -186,23 +184,12 @@ export class IVProgProcessor {
   }
 
   executeCommands (store, cmds) {
-    const auxExecCmdFun = (promise, cmd) => promise.then( sto => this.executeCommand(sto, cmd));
-    let breakLoop = false;
-    let $result = Promise.resolve(store);
-    for (let index = 0; index < cmds.length && !breakLoop; index++) {
-      const cmd = cmds[index];
-      $result = auxExecCmdFun($result, cmd);
-      $result.then(sto => {
-        if(sto.mode === Modes.RETURN) {
-          breakLoop = true;
-        } else if (this.checkContext(Context.BREAKABLE && 
-          sto.mode === Modes.BREAK)) {
-            breakLoop = true;
-        }
-        return sto;
-      });
-    }
-    return $result;
+    // helper to partially apply a function, in this case executeCommand
+    const partial = (fun, cmd) => (sto) => fun(sto, cmd);
+    return cmds.reduce((lastCommand, next) => {
+      const nextCommand = partial(this.executeCommand.bind(this), next);
+      return lastCommand.then(nextCommand);
+    }, Promise.resolve(store));
   }
 
   executeCommand (store, cmd) {
@@ -682,17 +669,18 @@ export class IVProgProcessor {
   evaluateUnaryApp (store, unaryApp) {
     const $left = this.evaluateExpression(store, unaryApp.left);
     return $left.then( left => {
-      if (!canApplyUnaryOp(unaryApp.op, left)) {
+      const resultType = resultTypeAfterUnaryOp(unaryApp.op, left.type);
+      if (resultType === Types.UNDEFINED) {
         // TODO: better urgent error message
         return Promise.reject(new Error(`Cannot use this op to ${left.type}`));
       }
-      switch (unaryApp.op) {
-        case Operators.ADD:
-          return new StoreObject(left.type, +left.value);
-        case Operators.SUB:
-          return new StoreObject(left.type, -left.value);
-        case Operators.NOT:
-          return new StoreObject(left.type, !left.value);
+      switch (unaryApp.op.ord) {
+        case Operators.ADD.ord:
+          return new StoreObject(resultType, +left.value);
+        case Operators.SUB.ord:
+          return new StoreObject(resultType, -left.value);
+        case Operators.NOT.ord:
+          return new StoreObject(resultType, !left.value);
         default:
         return Promise.reject(new Error('!!!Critical Invalid UnaryApp '+ unaryApp.op));
       }
@@ -705,37 +693,49 @@ export class IVProgProcessor {
     return Promise.all([$left, $right]).then(values => {
       const left = values[0];
       const right = values[1];
-      if (!canApplyInfixOp(infixApp.op, left, right)) {
+      const resultType = resultTypeAfterInfixOp(infixApp.op, left.type, right.type);
+      if (resultType === Types.UNDEFINED) {
         // TODO: better urgent error message
         return Promise.reject(new Error(`Cannot use this op to ${left.type} and ${right.type}`));
       }
-      switch (infixApp.op) {
-        case Operators.ADD:
-          return new StoreObject(left.type, left.value + right.value);
-        case Operators.SUB:
-          return new StoreObject(left.type, left.value - right.value);
-        case Operators.MULT:
-          return new StoreObject(left.type, left.value * right.value);
-        case Operators.DIV:
-          return new StoreObject(left.type, left.value / right.value);
-        case Operators.MOD:
-          return new StoreObject(left.type, left.value % right.value);
-        case Operators.GT:
-          return new StoreObject(Types.BOOLEAN, left.value > right.value);
-        case Operators.GE:
-          return new StoreObject(Types.BOOLEAN, left.value >= right.value);
-        case Operators.LT:
-          return new StoreObject(Types.BOOLEAN, left.value < right.value);
-        case Operators.LE:
-          return new StoreObject(Types.BOOLEAN, left.value <= right.value);
-        case Operators.EQ:
-          return new StoreObject(Types.BOOLEAN, left.value === right.value);
-        case Operators.NEQ:
-          return new StoreObject(Types.BOOLEAN, left.value !== right.value);
-        case Operators.AND:
-          return new StoreObject(Types.BOOLEAN, left.value && right.value);
-        case Operators.OR:
-          return new StoreObject(Types.BOOLEAN, left.value || right.value);
+      let result = null;
+      switch (infixApp.op.ord) {
+        case Operators.ADD.ord:
+          return new StoreObject(resultType, left.value + right.value);
+        case Operators.SUB.ord:
+          return new StoreObject(resultType, left.value - right.value);
+        case Operators.MULT.ord: {
+          result = left.value * right.value;
+          if (resultType === Types.INTEGER)
+            result = Math.round(result);
+          console.log("R multi" + result);
+          return new StoreObject(resultType, result);
+        }
+        case Operators.DIV.ord: {
+          result = left.value / right.value;
+          if (resultType === Types.INTEGER)
+            result = Math.round(result);
+          console.log("R div:" +result);
+          return new StoreObject(resultType, result);
+        }
+        case Operators.MOD.ord:
+          return new StoreObject(resultType, left.value % right.value);
+        case Operators.GT.ord:
+          return new StoreObject(resultType, left.value > right.value);
+        case Operators.GE.ord:
+          return new StoreObject(resultType, left.value >= right.value);
+        case Operators.LT.ord:
+          return new StoreObject(resultType, left.value < right.value);
+        case Operators.LE.ord:
+          return new StoreObject(resultType, left.value <= right.value);
+        case Operators.EQ.ord:
+          return new StoreObject(resultType, left.value === right.value);
+        case Operators.NEQ.ord:
+          return new StoreObject(resultType, left.value !== right.value);
+        case Operators.AND.ord:
+          return new StoreObject(resultType, left.value && right.value);
+        case Operators.OR.ord:
+          return new StoreObject(resultType, left.value || right.value);
         default:
           return Promise.reject(new Error('!!!Critical Invalid InfixApp '+ infixApp.op));
       }

+ 10 - 0
tests/test42.spec.js

@@ -0,0 +1,10 @@
+import { resultTypeAfterInfixOp } from './../js/processor/compatibilityTable';
+import { Operators } from './../js/ast/operators';
+import { Types } from '../js/ast/types';
+
+describe("The compatbility table", () => {
+  it("should return the correct type for a given infix operator application", () => {
+    const type = resultTypeAfterInfixOp(Operators.ADD, Types.INTEGER, Types.REAL);
+    expect(type).toEqual(Types.REAL);
+  });
+});

+ 32 - 0
tests/test43.spec.js

@@ -0,0 +1,32 @@
+import { IVProgParser } from './../js/ast/ivprogParser';
+import { IVProgProcessor} from './../js/processor/ivprogProcessor'
+import { InputTest } from './../js/util/inputTest';
+import { LanguageService } from '../js/services/languageService';
+
+describe('The sum of a real and a integer', function () {
+
+  const code = `programa {
+
+    funcao inicio() {
+      real a;
+      leia(a);
+      a = a + 0xff
+    }
+  }`;
+
+  localStorage.setItem('ivprog.lang', 'pt');
+  const input = new InputTest(['5.8']);
+
+  const lexer = LanguageService.getCurrentLexer();
+
+  it(`should result in a real`, function (done) {
+    const parser = new IVProgParser(code, lexer);
+    const exec = new IVProgProcessor(parser.parseTree());
+    exec.registerInput(input);
+    exec.interpretAST().then(sto => {
+      expect(sto.applyStore('a').value).toEqual(5.8 + 0xff);
+      localStorage.removeItem('ivprog.lang');
+      done();
+    }).catch( err => done(err));
+  });
+});