{ Project1.pas Copyright (C) 2008 Dmitry 'Skalogryz' Boyarintsev main parser unit } program objcparser; {$ifdef fpc} {$mode delphi}{$H+} {$else} {$APPTYPE CONSOLE} {$endif} uses Classes, IniFiles, SysUtils, ObjCParserUtils, ObjCParserTypes; type // this object is used only for precomile directives handling { TPrecompileHandler } TPrecompileHandler = class(TObject) public hdr : TObjCHeader; procedure OnPrecompile(Sender: TObject); procedure OnComment(Sender: TObject; const Comment: AnsiString); constructor Create(AHeader: TObjCHeader); end; var updIni : AnsiString = ''; noConvert : Boolean = false; function FindMax(const c: array of Integer; len: Integer): Integer; var i : integer; mn : Integer; begin Result := -1; if len = 0 then Exit; mn := 0; for i := 1 to len - 1 do begin if c[i] < c[mn] then mn := i; end; Result := mn; end; procedure TPrecompileHandler.OnPrecompile(Sender: TObject); var parser : TTextParser; preEntity : TPrecompiler; lst : TEntity; prc : TNotifyEvent; begin parser := Sender as TTextParser; //todo: change for something nicier =) prc := parser.OnPrecompile; parser.OnPrecompile := nil; try if parser.Stack.Count > 0 then lst := TEntity(parser.Stack[parser.Stack.Count-1]) else lst := nil; preEntity := TPrecompiler.Create(lst); preEntity.Parse(parser); lst.Items.Add(preEntity); finally parser.OnPrecompile := prc; end; end; procedure TPrecompileHandler.OnComment(Sender: TObject; const Comment: AnsiString); var parser : TTextParser; cmt : TComment; ent : TEntity; begin if length(Comment) < 2 then Exit; parser := TTextParser(Sender); if parser.Stack.Count > 0 then ent := TEntity(parser.Stack[parser.Stack.Count-1]) else ent := nil; if not Assigned(ent) then Exit; cmt := TComment.Create(ent); cmt._Comment := Comment; if IsSubStr('/*', cmt._Comment, 1) then begin cmt._Comment[1] := '('; if isSubStr('*/', cmt._Comment, length(cmt._Comment) - 1) then cmt._Comment[ length(cmt._Comment)] := ')'; end; ent.Items.Add(cmt); end; constructor TPrecompileHandler.Create(AHeader: TObjCHeader); begin hdr := AHeader; end; procedure UpdateIniWithEntity(Sets: TConvertSettings; Ini: TIniFile; Entity: TEntity); var cnv : AnsiString; i : Integer; begin if Entity is TClassDef then begin Ini.WriteString('TypeDefs', TClassDef(Entity)._ClassName, 'objcclass'); end else if Entity is TStructTypeDef then begin Ini.WriteString('TypeDefs', TStructTypeDef(Entity)._Name, 'struct'); end else if Entity is TTypeNameDef then begin if Assigned(Sets) then begin cnv := AnsiLowerCase(ObjCToDelphiType(TTypeNameDef(Entity)._Inherited, false )); if (cnv = 'float') or (cnv = 'double') then Ini.WriteString('TypeDefs', TTypeNameDef(Entity)._TypeName, 'float') else if (cnv = 'Int64') then Ini.WriteString('TypeDefs', TTypeNameDef(Entity)._TypeName, 'struct') end; end; for i := 0 to Entity.Items.Count - 1 do UpdateIniWithEntity(Sets, Ini, Entity.Items[i]); end; function ReadAndParseFile(const FileName: AnsiString; outdata: TStrings; var Err: AnsiString): Boolean; var hdr : TObjCHeader; parser : TTextParser; prec : TPrecompileHandler ; s : AnsiString; i, cnt : integer; upini : TIniFile; begin Result :=false; if not FileExists(FileName) then begin Err := 'File not found: ' + FileName; Exit; end; s := StrFromFile(FileName); hdr := TObjCHeader.Create; prec := TPrecompileHandler.Create(hdr); parser := TTextParser.Create; parser.TokenTable := CreateObjCTokenTable; try parser.Buf := s; try parser.TokenTable.Precompile := '#'; parser.OnPrecompile := prec.OnPrecompile; parser.OnComment := prec.OnComment; parser.IgnoreTokens.AddStrings(ConvertSettings.IgnoreTokens); hdr._FileName := ExtractFileName(FileName); Result := hdr.Parse(parser); if not Result then begin if parser.Errors.Count > 0 then Err := parser.Errors[0] else Err := 'undesribed error'; Err := Err + #13#10; cnt := 120; i := parser.Index - cnt; if i <= 0 then begin i := 1; cnt := parser.Index; end; Err := Err + Copy(parser.Buf, i, cnt); end; except end; if updIni <> '' then begin upIni := TIniFile.Create(updIni); try UpdateIniWithEntity(ConvertSettings, upIni, hdr); finally upIni.Free; end; end; WriteOutIncludeFile(hdr, outdata); finally parser.TokenTable.Free; parser.Free; prec.Free; //FreeEntity(hdr); end; end; procedure ParseAll; var ch : char; srch : TSearchRec; res : Integer; i : Integer; pth : AnsiString; incs : AnsiString; st : TStringList; f : Text; err : AnsiString; begin err := ''; writeln('would you like to parse all current directory files .h to inc?'); readln(ch); if (ch <> 'Y') and (ch <> 'y') then begin writeln('as you wish, bye!'); Exit; end; pth := IncludeTrailingPathDelimiter( GetCurrentDir); writeln('looking for .h files in ', pth); res := FindFirst(pth + '*.h', -1, srch); if res = 0 then begin st := TStringList.Create; try repeat write('found: ', srch.Name); write(' parsing...'); //writeln('parsing: ', pth+srch.Name); if ReadAndParseFile(pth+srch.Name, st, err) then begin write(' parsed '); incs := pth + Copy(srch.Name,1, length(srch.Name) - length(ExtractFileExt(srch.Name))); incs := incs + '.inc'; //writeln(incs); assignfile(f, incs); rewrite(f); try for i := 0 to st.Count - 1 do writeln(f, st[i]); finally closefile(f); end; st.Clear; writeln(' converted!'); end else begin writeln('Error: ', err); end; until FindNext(srch) <> 0; finally FindClose(srch); st.Free; end; end; end; const ParamKey = '-'; function isParamValue(const s: AnsiString; var ParName, ParValue: AnsiString): Boolean; var i : Integer; begin Result := false; if s = '' then Exit; Result := (s[1] = ParamKey); if not Result then Exit; i := 1; ScanWhile(s, i, [ParamKey]); ParName := ScanTo(s, i, [#32, #9, '=']); ScanWhile(s, i, [#32, #9, '=']); ParValue := Copy(s, i, length(s) - i + 1); end; procedure AddSpaceSeparated(const s: AnsiString; Strings: TStringList); var i : Integer; ns : AnsiString; begin i := 1; while i <= length(s) do begin ScanTo(s, i, ['A'..'Z', 'a'..'z']); ns := ScanTo(s, i, [#32, #9, '"']); if ns <> '' then Strings.Add(ns); end; end; function isNameofPointer(const name: AnsiString): Boolean; begin Result := false; if name = '' then Exit; Result := name[length(name)] = '*'; end; procedure ReadIniFile(Settings: TConvertSettings; const FileName: AnsiString); var ini : TIniFile; values : TStringList; a, b : AnsiString; i : Integer; begin // uikit.ini if not FileExists(FileName) then begin writeln('//ini file is not found'); Exit; end; ini := TIniFile.Create(FileName); values := TStringList.Create; try values.Clear; { ini.ReadSection('TypeReplace', values); for i := 0 to values.Count - 1 do begin a := values.ValueFromIndex[i]; b := values.Values[a]; if b <> '' then begin ense Settings.TypeDefReplace[a] := b; end;} values.Clear; //ini.ReadSectionValues('ReplaceToken', values); ini.ReadSection('ReplaceToken', values); for i := 0 to values.Count - 1 do begin a := Values[i]; b := ini.ReadString('ReplaceToken', a, ''); if b ='' then Settings.IgnoreTokens.Add(a); end; values.Clear; ini.ReadSection('TypeDefs', values); for i := 0 to values.Count - 1 do begin a := Values[i]; b := AnsiLowerCase(ini.ReadString('TypeDefs', a, '')); if b = 'objcclass' then Settings.ObjCTypes.Add(a) else if b = 'struct' then Settings.StructTypes.Add(a) else if b = 'float' then Settings.FloatTypes.Add(a); end; values.Clear; ini.ReadSection('TypeReplace', values); for i := 0 to values.Count - 1 do begin a := Values[i]; b := ini.ReadString('TypeReplace', a, ''); if isNameofPointer(a) then Settings.PtrTypeReplace[ Copy(a, 1, length(a) - 1)] := b else Settings.TypeDefReplace[a] := b end; finally values.Free; ini.Free; end; end; function GetConvertSettings(Settings : TConvertSettings; var FileName: AnsiString): Boolean; var i : integer; prm : AnsiString; vlm : AnsiString; Params : TStringList; begin prm := ''; vlm := ''; Params := TStringList.Create; Params.CaseSensitive := false; try for i := 1 to ParamCount do begin if isParamValue(ParamStr(i), prm, vlm) then begin prm := AnsiLowerCase(prm); if prm = 'mu' then prm := 'mainunit' else if prm = 'ii' then prm := 'ignoreinclude'; Params.Values[prm] := vlm; end else FileName := ParamStr(i); end; vlm := Params.Values['ini']; if vlm <> '' then ReadIniFile(Settings, vlm); vlm := Params.Values['mainunit']; if vlm <> '' then Settings.ConvertPrefix.Add ('{%mainunit '+vlm+'}'); vlm := Params.Values['ignoreinclude']; if vlm <> '' then AddSpaceSeparated(vlm, Settings.IgnoreIncludes); vlm := Params.Values['updini']; if vlm <> '' then updIni := vlm; finally Params.Free; end; Result := true; end; var inpf : AnsiString = ''; st : TStrings = nil; err : AnsiString = ''; i : integer; begin try GetConvertSettings(ConvertSettings, inpf); if not FileExists(inpf) then begin ParseAll; Exit; end; st := TStringList.Create; try if not ReadAndParseFile(inpf, st, err) then writeln('Error: ', err) else for i := 0 to st.Count - 1 do writeln(st[i]); except end; st.Free; except on e: exception do writeln(e.Message); end; end.