Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

DSL на JavaScript для C либо кодгенератор — это легко!

Anna | 24.06.2014 | нет комментариев
Ковырялся давеча с одним универсальным, и потому до неприличного сильным, интерфейсом доступа к данным на Python-е. Неприличная мощь выражается в виде множества параметров на все случаи жизни, нередко весьма эксцентричные и надобные только в 5% случаев. В результате доводится дублировать всю пачку параметров и деталей даже в откровенных запросах, что вызывает пессимизм и желание заняться чем-то иным. И здесь вспомнилась мне аналогичная история из моего дальнего прошлого, которой и делюсь.

Задача

Было это давным-давно и не правда, настоль давным-давно, что дозволено отважно относить в категорию мемуаров. Отрядили нас в поддержка отделу занимающемуся аутсорсингом. План был теснее в стадии энергичного кодирования и, что-то обговаривать и менять было теснее поздно (а может и первоначально немыслимо). Нужно было сделать интерфейс бизнес яруса к данным, хранящимся в 20 табличках. На С . Под винду и линукс. 20 табличек в Постгресе либо Оракле. «Интерфейс бизнес яруса» это такой эвфемизм к тупейшему комплекту операций над сущностями из этих таблиц, а именно — предпочесть по ключу либо значению других полей, сделать, изменить либо предпочесть связные сущности.
К тому моменту я теснее начинал предполагать, что наша работа изредка бывает не дюже увлекательной, но Дабы настоль! Эта задача по своей унылости побила все рекорды. В одночасье я ощутил себя в шкуре тех людей которым их работа не увлекательна, и меня это, отчего-то, вовсе не воодушевляло. На этом фоне у моих коллег все было не так нехорошо — они, каждого лишь, перебрали кучу С (С) либ упрощающих доступ к БД и удостоверились в том, что ни одна из них, против заявлениям, типично не работает единовременно с Ораклом и Постгресом.

Идея

Уверенность в том, что это тупую работу должен делать кто желательно, но только не я, помаленьку подталкивала меня к мысли, что нужно на кого-нить это свалить. Из моего окружения смириться с такой участью согласился только компьютер, так что, в выводе, ему вывалилась честь сгенерировать нужный код. Напрягало во каждой этой затее только то, что на тот момент о предмете у меня было крайне смутное представление как о чем-то дюже трудном и нетривиальном. Из инкубационного периода идею с генерацией вывело случайное воспоминание о дебуге одного вэб приложения с JSP, бинами и прочим ужасом летящим на крыльях ночи. Дебугер показал мне код тот, что генерится из JSP (java server pages) странички. К примеру из такой вот странички:

<ul>
<% for (int i = 0; i < 10; i  ) { %>
  <li> <%= i %>
<% } %>
</ul>

получается приблизительно дальнейший код:

out.println("<ul>");
for (int i = 0; i < 10; i  ) {
  out.print("<li>");
  out.print(i);
  out.println();
}
out.println("</ul>");

То есть, алгорифм генерации из JSP кода тот, что, в свою очередь, генерит HTML дюже примитивен, примерно: все, что внутри <% %> становится кодом, а все, что снаружи заворачивается в out.print(“<текст>”), ну и немножко синтаксического сахара. В нашем случае необходимо лишь заменить HTML на С , а жаву на что-то еще и, позже простейших реформирований, мы получаем код, тот, что нам сгенерит желаемый C код. «Что-то еще» я предпочел по тезису наименьшего сопротивления — windows scripting host теснее был на нашей билд машине, соответственно, используем java script (либо ECMAScript, как его там).

Прототип

В данной статье мы разглядим максимально «близкое к тексту» решение. Подлинное решение, к сожалению, разглядеть не получится — слишком много значимых деталей теснее стерлось из памяти. Будем считать, что БД у нас только Постгрес, Дабы не покидать комфортабельный линух энвайрмент windows scripting host тоже не будем применять, в качестве данных будем применять какую-нить простенькую типовую схемку с Employee, Department etc.
В качестве standalone реализации JS возьмем 1-й пришедший в голову — rhino (отчего-то v8 был вторым). Выходит, ставим rhino, создаем файл codegen.js, пишем туда print(2*2); rhino codegen.js: 4, voila!

codegen.js

if (arguments.length < 1)
{
  print("Usage: codegen.js <template>");
  quit();
}

function produce_text(text)
{
  return "__codegen_output  = '"   text.replace("'", "\'", 'g').replace('n', '\n', 'g').replace('r', '\r', 'g').replace('t', '\t', 'g')   "';n";
}

function produce_code(code)
{
  if (code[0] == '=')
  {
    return '__codegen_output  = '   code.substr(1)   ';n';
  }
  return code   'n';
}

remainder = readFile(arguments[0]);
var code = 'var __codegen_output = ""; ';
while (remainder.length)
{
  var begin = remainder.indexOf('<%');
  if (begin >= 0)
  {
    code  = produce_text(remainder.substr(0, begin));
    remainder = remainder.substr(begin   2);
    var end = remainder.indexOf('%>');
    if (end >= 0)
    {
      code  = produce_code(remainder.substr(0, end));
      remainder = remainder.substr(end   2);
    }
    else
    {
      code  = produce_code(remainder);
      remainder = '';
    }
  }
  else
  {
    code  = produce_text(remainder);
    remainder = '';
  }
}
code  = 'print(__codegen_output);'

eval(code);

— тупейшая, откровенная реализация описанного чуть выше алгорифма — 50 строк — каждый кодогенератор. Тестируем:
файл template:

<%
var className = 'MyClass';
var fields = ['Name', 'Description', 'AnotherOne', 'LastOne'];
%>

class <%= className %>
{
  private:
  <% for(var i = 0; i < fields.length; i  ) { %>
    int <%= fields[i] %>;
  <% } %>
};
rhino codegen.js tempalte:
class MyClass
{
  private:

    int Name;

    int Description;

    int AnotherOne;

    int LastOne;

};

В <% %> у нас всякий js код, <%= expr %> заменяется итогом вычисления expr. В тезисе — это все, что нам нужно, того довольно, Дабы сгенерить вообще все, что желательно. К сожалению, стоит подметить, что это простота имеет и обратную сторону — код в образце дюже плотный и без подходящей подсветки синтаксиса читать его трудно. Не последнюю скрипку в этом играет сам JS — лаконичностью и выразительностью он не отличается.
Сейчас время испробовать сгенерить что-то больше пригодное.
файл template:

<%
var model = [
  {
    name: 'Employee',
    fields: {
      Id: { type: 'int' },
      Name: { type: 'string' }
    }
  }
];

var cppTypeMap = {
  'int': 'int',
  'string': 'std::string'
};
%>

<% for (var i = 0; i < model.length; i  )
   {
     var entity = model[i];%>
     struct <%= entity.name %>
{
  <% for (var field in entity.fields)
     { %>
  <%= cppTypeMap[entity.fields[field].type] %> <%= field %>;
  <% } %>
};
<% } %>
rhino codegen.js template:
struct Employee
{

  int Id;

  std::string Name;

};

Содержимое переменной model это, по сути, так называемый DSL (domain specific language). В нашем случае это язык изложения сущностей предметной области. Нынешний его вариант слишком прост Дабы быть хоть как-то пригодным, так что дополним его каждому нужным.

var model = [
  {
    name: 'Department',
    fields: {
      Id: { type: 'int' },
      Name: { type: 'string'}
    },
    primaryKey: 'Id'
  },
  {
    name: 'Employee',
    fields: {
      Id: { type: 'int' },
      Name: { type: 'string' },
      DepartmentId: { type: 'int', references: 'Department' }
    },
    primaryKey: 'Id'
  }
];

Решение

Сейчас мы в состоянии сгенерить код приобретения сущностей из БД по ключу, по связным сущностям и т.д. Также теснее дозволено сгенерить sql скрипт для создания базы данных. Дабы из одной модели генерить различные исходники немножко поразбросаем код:

model

var model = [
  {
    name: 'Department',
    fields: {
      Id: { type: 'int' },
      Name: { type: 'string'}
    },
    primaryKey: 'Id'
  },
  {
    name: 'Employee',
    fields: {
      Id: { type: 'int' },
      Name: { type: 'string' },
      DepartmentId: { type: 'int', references: 'Department' }
    },
    primaryKey: 'Id'
  }
];

будет содержать только наш DSL,

cpp.template

<%

load('model');

var cppTypeMap = {
  'int': 'int',
  'string': 'std::string'
};

function fieldType(entity, field)
{
  return cppTypeMap[entity.fields[field].type];
}

%>

<% for (var i = 0; i < model.length; i  )
   {
      var entity = model[i];%>
struct <%= entity.name %>
{
  <% for (var field in entity.fields)
     { %>
  <%= fieldType(entity, field) %> <%= field %>;
  <% } %>
  <% var fieldList = [];
     for (var field in entity.fields)
       fieldList.push(field);
  %>
  static <%= entity.name %> ByKey(<%= fieldType(entity, entity.primaryKey) %> key, pqxx::work& tr)
  {
    if (!tr.prepared("<%= entity.name %>ByKey").exists())
    {
      tr.conn().prepare("<%= entity.name %>ByKey",
         "select <%= fieldList.join() %> from <%= entity.name %> where <%= entity.primaryKey %> = $1");
    }
    pqxx::result rows = tr.prepared("<%= entity.name %>ByKey")(key).exec(query);
    <%= entity.name %> result;
    <% for (var j = 0; j < fieldList.length; j  )
       { %>
    result.<%= fieldList[j] %> = rows[0][<%= j %>].as<<%= fieldType(entity, fieldList[j]) %>>();
    <% } %>
    return result;
  }

  <% for (var field in entity.fields)
       if (entity.fields[field].references)
       { 
         var ref = entity.fields[field].references; %>
  <%= ref %> Get<%= ref %>()
  {
    return <%= ref %>::ByKey(<%= field %>);
  }

  static std::vector<<%= entity.name %>> By<%= ref %>(<%= fieldType(entity, field) %> key)
  {
    if (!tr.prepared("<%= entity.name %>By<%= ref %>").exists())
    {
      tr.conn().prepare("<%= entity.name %>By<%= ref %>",
         "select <%= fieldList.join() %> from <%= entity.name %> where <%= field %> = $1");
    }
    pqxx::result rows = tr.prepared("<%= entity.name %>By<%= ref %>")(key).exec(query);
    std::vector<<%= entity.name %>> result;
    for (pqxx::result::size_type i = 0; i < rows.size(); i  )
    {
      <%= entity.name %> row;
    <% for (var j = 0; j < fieldList.length; j  )
       { %>
      row.<%= fieldList[j] %> = rows[i][<%= j %>].as<<%= fieldType(entity, fieldList[j]) %>>();
    <% } %>
      result.push_back(row);
    }
    return result;
  }
  <%   } %>
};
<% } %>

— образец для генерации плюсового кода и

sql.template

<%

load('model');

var sqlTypeMap = {
  'int': 'integer',
  'string': 'text'
};

function fieldType(entity, field)
{
  return sqlTypeMap[entity.fields[field].type];
}

%>

<% for (var i = 0; i < model.length; i  )
   {
      var entity = model[i];%>
CREATE TABLE <%= entity.name %> (
  <% for (var field in entity.fields)
     {
       var ref = entity.fields[field].references; %>
  <%= field %> <%= fieldType(entity, field) %><% if (ref) { %> REFERENCES <%= ref %><% } %>,
  <% } %>
  PRIMARY KEY (<%= entity.primaryKey %>)
);
<% } %>

— образец для генерации скрипта создания БД.

rhino codegen.js cpp.template:
struct Department
{

  int Id;

  std::string Name;

  static Department ByKey(int key, pqxx::work& tr)
  {
    if (!tr.prepared("DepartmentByKey").exists())
    {
      tr.conn().prepare("DepartmentByKey",
         "select Id,Name from Department where Id = $1");
    }
    pqxx::result rows = tr.prepared("DepartmentByKey")(key).exec(query);
    Department result;

    result.Id = rows[0][0].as<int>();

    result.Name = rows[0][1].as<std::string>();

    return result;
  }

};

struct Employee
{

  int Id;

  std::string Name;

  int DepartmentId;

  static Employee ByKey(int key, pqxx::work& tr)
  {
    if (!tr.prepared("EmployeeByKey").exists())
    {
      tr.conn().prepare("EmployeeByKey",
         "select Id,Name,DepartmentId from Employee where Id = $1");
    }
    pqxx::result rows = tr.prepared("EmployeeByKey")(key).exec(query);
    Employee result;

    result.Id = rows[0][0].as<int>();

    result.Name = rows[0][1].as<std::string>();

    result.DepartmentId = rows[0][2].as<int>();

    return result;
  }

  Department GetDepartment()
  {
    return Department::ByKey(DepartmentId);
  }

  static std::vector<Employee> ByDepartment(int key)
  {
    if (!tr.prepared("EmployeeByDepartment").exists())
    {
      tr.conn().prepare("EmployeeByDepartment",
         "select Id,Name,DepartmentId from Employee where DepartmentId = $1");
    }
    pqxx::result rows = tr.prepared("EmployeeByDepartment")(key).exec(query);
    std::vector<Employee> result;
    for (pqxx::result::size_type i = 0; i < rows.size(); i  )
    {
      Employee row;

      row.Id = rows[i][0].as<int>();

      row.Name = rows[i][1].as<std::string>();

      row.DepartmentId = rows[i][2].as<int>();

      result.push_back(row);
    }
    return result;
  }

};

Сознаюсь, что сгенеренный код не запускал и даже не компилировал, но обещаю, что он дюже близок к реальному коду работающему с постгресом. :)

rhino codegen.js sql.template:
CREATE TABLE Department (

  Id integer,

  Name text,

  PRIMARY KEY (Id)
);

CREATE TABLE Employee (

  Id integer,

  Name text,

  DepartmentId integer REFERENCES Department,

  PRIMARY KEY (Id)
);

Для многих, я думаю, видимо, что ничего нового я не придумал. Уйма ORM’ов может генерить подобный код. Основная цель статьи была продемонстрировать, что сделать свой язык, пускай каждого лишь DSL, не легко, а дюже легко. Это вовсе не так жутко, как кажется, потому как на многих этапах дозволено дюже недурно сэкономить. Скажем, в данном случае мы сэкономили на парсере — огромную часть работы делает JS движок, и на компиляторе — генерить плюсовый код гораздо проще, чем машинный, а тяжести пускай таскает плюсовый компилятор.

Источник: programmingmaster.ru

Оставить комментарий
БАЗА ЗНАНИЙ
СЛУЧАЙНАЯ СТАТЬЯ
СЛУЧАЙНЫЙ БЛОГ
СЛУЧАЙНЫЙ МОД
СЛУЧАЙНЫЙ СКИН
НОВЫЕ МОДЫ
НОВЫЕ СКИНЫ
НАКОПЛЕННЫЙ ОПЫТ
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB