unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,
  regexpr;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    generate: TButton;
    Edit1: TEdit;
    Memo2: TMemo;
    parse: TButton;
    Label1: TLabel;
    Label2: TLabel;
    Button1: TButton;
    procedure generateClick(Sender: TObject);
    procedure parseClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type
  tstrpair = record
    sFrom: string;
    sTo: string;
  end;

const
  conversionTypes: array [0..1] of tstrpair = (
    (sFrom: 'str'; sTo: 'RawUTF8'),
    (sFrom: 'int'; sTo: 'integer')
  );

function aliasToType(s: string) : string ;
var i: integer;
begin
  result:= s;
  s:= lowercase(s);
  for i:= low(conversionTypes) to high(conversionTypes) do
    if conversionTypes[i].sFrom = s then
      begin
        result:= conversionTypes[i].sTo;
        break;
      end;
end;

function typeToAlias(s: string) : string ;
var i: integer;
begin
  result:= s;
  s:= lowercase(s);
  for i:= low(conversionTypes) to high(conversionTypes) do
    if lowercase(conversionTypes[i].sTo) = s then
      begin
        result:= conversionTypes[i].sFrom;
        break;
      end;
end;

procedure TForm1.generateClick(Sender: TObject);
var
 r: TRegExpr;
 clsname,
 priv, publ,
 functs, impl,
 stype, sname,
 fwrite, fread: string;
begin
clsname:= 'TSQL'+edit1.Text;

r:= TRegExpr.Create;
r.Expression:= '(\s+)?(\w+)(\s+)?:(\s+)?(\w+)(\s+)?(\;r)?(\;w)?';
if r.Exec(memo1.Text) then
  repeat
    stype:= aliasToType( r.match[5] );
    sname:= r.match[2];

    if (length(r.Match[7]) > 0) then
      begin
        fread := 'Get'+sname;
        functs:= functs + #13#10#9#9 + format('function Get%s():%s;'#13#10, [sname, stype]);
        impl:= impl + #13#10 + format('function %s.Get%s():%s;'#13#10'begin'#13#10#9'result:= f%s;'#13#10'end;'#13#10, [clsname, sname, stype, sname]);
      end
    else
      fRead:= 'f'+sname;

    if (length(r.Match[8]) > 0) then
      begin
        fwrite := 'Set'+sname;
        functs:= functs + #13#10#9#9 + format('procedure Set%s(const AValue: %s);'#13#10, [sname, stype]);
        impl:= impl + #13#10 + format('procedure %s.Set%s(const AValue: %s);'#13#10'begin'#13#10#9'f%s:= AValue;'#13#10'end;'#13#10, [clsname, sname, stype, sname]);
      end
    else
      fWrite:= 'f'+sname;

    priv:= priv + #9#9 + format('f%s: %s;'#13#10, [sname, stype]);
    publ:= publ + #9#9 + format('property %s: %s read %s write %s;'#13#10, [sname, stype, fread, fwrite]);
  until not r.ExecNext;
memo2.Text:=
format(
  #9'%s = class(TSQLRecord)'#13#10 +
  #9'private'#13#10'%s'#13#10'%s'+
  #9'published'#13#10'%s'#13#10+
  #9'end;'#13#10#13#10#13#10'%s',
  [clsname, priv, functs, publ, impl]);
end;

procedure TForm1.parseClick(Sender: TObject);
var
 r: TRegExpr;
 def: string;
begin
r:= TRegExpr.Create;

r.Expression:= '\s+?TSQL(\w+)\s*=\s*class';
if r.Exec(memo2.Text) then
  edit1.Text:= r.Match[1];

r.Expression:= 'property\s+(\w+)(\s+)?:(\s+)?(\w+)\s*(read (\w+))?\s*(write (\w+))?';
//r.Expression:= '(\w+):(\w+)';
//r.ModifierM
if r.Exec(memo2.Text) then
  repeat
    def:= def + r.Match[1] + ':'+ typeToAlias( r.Match[4] );
    //showmessage(r.Match[6]);
    if Copy(r.match[6], 1, 3) = 'Get' then
      def:= def + ';r';
    if Copy(r.match[8], 1, 3) = 'Set' then
      def:= def + ';w';

    def:= def + #13#10;
  until not r.ExecNext;
memo1.Text:=def;
end;

procedure TForm1.Button1Click(Sender: TObject);
const
  sHelp =
    'This simple application speeds up defining SQL Records classes for Synopse mORMot Framework'#13#10+
    'Since some versions of Delphi (including mine) does not have class completion, declaring properties,'#13#10+
    'their setters / getters and so on is a huge waste of time.'#13#10+
    'Thus I''ve written this simple tool which introduces so-called meta-language for defining SQL Records'#13#10+
    'which is later converted to Delphi code.'#13#10+
    'It is simply list of field declarations we want to have in our SQL Record.'#13#10#13#10+
    'Syntax:'#13#10+
    'each line is a declaration of one field. It consists of field name and it''s type'#13#10+
    'the type might be shortened using aliases (see below).'#13#10+
    'additionally, each field can be marked with ";r" and/or ";w" flags'#13#10+
    'which will make the code generator to use respectively getter and/or setter for the specific field'#13#10+
    'The tool also makes it possible to parse back from Delphi class declaration to meta-code'#13#10+
    'Please note this will only work if the Delphi class follows naming pattern used in meta2pas generatorion'#13#10+
    #13#10'List of available aliases:'#13#10'%s'#13#10+
    '================'#13#10+
    'enjoy!'#13#10+
    'Michal migajek Gajek'#13#10'http://migajek.com'#13#10'migajek@gmail.com';
var
  i: integer;
  tmp: string;
begin
  for i:= low(conversionTypes) to high(conversionTypes) do
    tmp:= tmp + conversionTypes[i].sFrom + ' = ' + conversionTypes[i].sTo + #13#10;

  MessageBox(handle, PChar(Format(sHelp, [tmp])), 'Help', MB_ICONINFORMATION);
end;

end.
