{----------------------------------------------------------------------------
The contents of this file are subject to the Maguma Public License Version
1.0 ("License"); You may not use this file except in compliance with the
License. You may obtain a copy of the License at http://www.maguma.com/MaPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
the specific language governing rights and limitations under the License.
The Original Code is: Maguma Open Studio
The Initial Developer of the Original Code is Maguma, GmbH/Srl
Portions created by Maguma Open Studio are
Copyright (C) 2004,2005 Maguma, GmbH/Srl;
All Rights Reserved.
Contributors listed in the contributors.txt file included with the
distribution.
----------------------------------------------------------------------------
$Id: PHPCodeParser.pas,v 1.1 2005/03/09 09:29:28 spooker Exp $
You may retrieve the latest version of this file at the Open Studio home
page, located at http://sourceforge.net/projects/openstudio/
----------------------------------------------------------------------------}
unit PHPCodeParser;
{$I PHPC.INC}
interface
uses Classes, PHPCodeElement, PHPDocCommentParser, Dialogs;
function ParsePHPCode(const str: String; InPHP: Boolean = False): TPHPDocument;
implementation
uses SysUtils, MiscUtils;
const WHITE: set of Char = [#13, #10, #9, ' '];
(* ========================================================================== *)
procedure _ParsePHPCode(const str: String; var Root: TPHPCodeElement;
CharPos: Integer; var InPHP: Boolean);
var
len, i, p, bracelev: Integer;
newel: TPHPCodeElement;
quote: Char;
InArgs: Boolean;
// funcarg: String;
procedure SkipWhite;
begin
while (i <= len) and (str[i] in WHITE) do inc(i);
end;
{$IFDEF PHPDOC}
// Find the PHP Doc Comment before a code element
function FindPHPDocComment(chrpos: Integer): TPHPDocComment;
var
iend, indnt: Integer;
begin
//Result := nil;
Result := TPHPDocComment.Create;
Result.FirstLineStartChar := CharPos + chrpos + 1;
Result.LastLineEndChar := CharPos + chrpos + 1;
// Find the indentation of the function, later, if there was a comment already,
// we find the actual indent of the Comment, but we need this first if there
// is no comment
indnt := chrpos;
while (indnt > 0) and not (str[indnt] in [#13, #10]) do
dec(indnt);
indnt := chrpos - indnt - 1;
if indnt > 100 then // what happened?
indnt := 24; // should be more than enough!
Result.Indent := indnt + 1;
while (chrpos > 0) and (str[chrpos] in WHITE) do dec(chrpos);
dec(chrpos); // we should see a '*/' now!
if (chrpos <= 0) or not (KeyComp('*/', @str[chrpos])) then Exit;
// Set iend to end of line
iend := chrpos;
while (iend <= len) and not (str[iend] in [#13, #10]) do inc(iend);
// Find the beginning of the comment
while (chrpos > 0) and not ((str[chrpos] = '/') and (str[chrpos + 1] = '*')) do
dec(chrpos);
if chrpos = 0 then Exit;
// Find the indentation of the comment
indnt := chrpos;
while (indnt > 0) and not (str[indnt] in [#13, #10]) do
dec(indnt);
indnt := chrpos - indnt - 1;
if indnt > 100 then // what happened?
indnt := 24; // should be more than enough!
Result.Indent := indnt;
// Find start of line
while (chrpos > 0) and not (str[chrpos] in [#13, #10]) do dec(chrpos);
inc(chrpos);
// Include the last CRLFs into iend as well so that they're erased
while (iend <= len) and (str[iend] in [#13, #10]) do inc(iend);
//Result := TPHPDocComment.Create;
Result.FirstLineStartChar := CharPos + chrpos;
Result.LastLineEndChar := CharPos + iend;
Result.CommentString := trim(copy(str, chrpos, iend - chrpos));
end;
{$ENDIF}
begin
len := Length(str);
i := 1;
while i <= len do
begin
Case str[i] of
'i', 'I', 'r', 'R': (* Include[Once] *)
if InPHP then
begin
if (KeyComp('include', @str[i]) or KeyComp('include_once', @str[i]) or
KeyComp('require', @str[i]) or KeyComp('require_once', @str[i])) // and we need whitespace in front of it!
and (i > 1) and (str[i - 1] in WHITE) then
begin
p := i;
while (i <= len) and (str[i] <> ';') do
inc(i);
newel := TPHPInclude.Create(Copy(str, p, i - p), CharPos + p - 1, i - p, Root);
Root.AddChild(newel);
end;
end;
'c', 'C': (* Class *)
if InPHP then
begin
if KeyComp('class', @str[i]) // and we need whitespace in front of it!
and (i > 1) and (str[i - 1] in WHITE) then
begin
p := i;
if ((i > 0) and (not(str[i - 1] in WHITE))) or (not(str[i + 5] in WHITE)) then
begin inc(i); continue; end;
// Setup the class node
while (i <= Length(str)) and (not (str[i] in ['{', ';'])) do inc(i);
newel := TPHPClass.Create(copy(str, p, i - p), CharPos + p - 1, i - p, Root);
Root.AddChild(newel);
{$IFDEF PHPDOC}
// Parse possible PHPDoc Comment
TPHPClass(newel).PHPDocComment := FindPHPDocComment(p - 1);
{$ENDIF}
// now parse the class code, find end of class def first and then recurse into this function again
bracelev := 0;
p := i;
while (i < len) and ((str[i] <> '}') or (bracelev > 1)) do
begin
if str[i] = '{' then
inc(bracelev)
else if str[i] = '}' then
dec(bracelev);
inc(i);
end;
_ParsePHPCode(copy(str, p, i - p + 1), newel, CharPos + p - 1, InPHP);
TPHPClass(newel).ClassLength := newel.Length + i - p + 5;
end;
end;
'f', 'F': (* Function *)
if InPHP then
begin
if KeyComp('function', @str[i]) // and we need whitespace in front of it!
and (i > 1) and (str[i - 1] in WHITE) and (Length(str) > (i+8)) and
(str[i+8] <> '_') then
begin
p := i;
// jump to the func args
while (i < len) and (str[i] <> '(') do inc(i);
// Function args
while (i < len) and (str[i] <> ')') do
begin
if (str[i] = '$') then
begin
// skip var name and any whitespace
inc(i);
while (i < len) and ((str[i] in ['A'..'Z', 'a'..'z', '0'..'9'])
or (str[i] in WHITE)) do inc(i);
// if (str[i] = '(') then InArgs := True;
// if (str[i] = ')') and (not InArgs) then break;
// if (str[i] = ')') and (InArgs) then InArgs := False;
if (str[i] = ')') then break;
inc(i);
if (i < len) and (str[i-1] = '=') then
begin // var has default value. Overstep it.
while (i < len) and (str[i] in WHITE) do inc(i);
if (i < len) and ((str[i] = '"') or (str[i] = '''')) then
begin
if (str[i] = '"') then quote := '"'
else quote := '''';
inc(i);
while (i < len) and (str[i-1] <> '\') and (str[i] <> quote) do inc(i);
inc(i);
end
else // not a quoted value
begin
while (i < len) and (not (str[i] in [',',')'])) do
begin
if str[i] = '(' then
begin
inc(i);
while (i < len) and (not (str[i] <> ')')) do inc(i);
end;
inc(i);
end;
end;
end;
end;
if (i < len) and (str[i] = ')') then break;
inc(i);
end;
if i > len then
i := len - 1;
// funcname := funcname;
newel := TPHPFunction.Create(Copy(str, p, i - p + 1), CharPos + p - 1, i - p + 1, Root);
Root.AddChild(newel);
{$IFDEF PHPDOC}
// Parse possible PHPDoc Comment
TPHPFunction(newel).PHPDocComment := FindPHPDocComment(p - 1);
{$ENDIF}
// jump to beginning of func
while (i < len) and (str[i] <> '{') do inc(i);
inc(i);
p := i;
bracelev := 1;
while (i < len) and (bracelev > 0) do
begin
if str[i] = '{' then
inc(bracelev)
else if str[i] = '}' then
dec(bracelev);
inc(i);
end;
TPHPFunction(newel).FunctionLength := newel.Length + i - p + 2;
_ParsePHPCode(copy(str, p, i - p + 1), Root, CharPos + p - 1, InPHP);
end;
end;
'v', 'V': (* var *)
begin
if (Root.ClassType = TPHPClass) and (i > 1) and (i+3 < len) and
KeyComp('var', @str[i]) and (str[i - 1] in WHITE) and
(str[i + 3] in WHITE) then
begin
p := i;
while (i < len) and (str[i] <> ';') do
inc(i);
newel := TPHPVariable.Create(copy(str, p, i - p + 1),
CharPos + p - 1, i - p + 1, nil);
Root.AddChild(newel);
{$IFDEF PHPDOC}
// Parse possible PHPDoc Comment
TPHPVariable(newel).PHPDocComment := FindPHPDocComment(p - 1);
{$ENDIF}
end;
end;
'<': (* PHP Open tag *)
begin
if KeyComp('<?', @str[i]) or KeyComp('<?php', @str[i]) then
InPHP := True;
end;
'>': (* PHP Close tag *)
begin
if ((i > 2) and KeyComp('?>', @str[i - 1])) then
InPHP := False;
end;
'/', '#': (* Comments *)
if InPHP then
begin
(* /* .. */ comments *)
if (i < len) and (str[i + 1] = '*') then
begin
while (i < len) and (not KeyComp('*/', @str[i])) do
inc(i)
end
(* // and # comments *)
else
if (str[i] = '#') or ((i < len) and (str[i] = '/') and (str[i + 1] = '/')) then
while (i < len) and (not (str[i] in [#13, #10])) do
inc(i);
end;
(* Strings *)
'"':
if InPHP then
begin
p := i - 1;
while (p > 0) and (str[p] = '\') do dec(p);
// The string delim char has only been escaped if there's an uneven number of \'s before
if ((i - p + 1) mod 2) = 0 then
begin
inc(i);
p := 0;
while (i < len) and (p = 0) do
begin
if str[i] = '"' then
begin
p := i - 1;
while (p > 0) and (str[p] = '\') do dec(p);
p := (i - p) mod 2;
end;
inc(i);
end;
end;
end;
'''':
if InPHP then
begin
inc(i);
while (i < len) and (str[i] <> '''') do
inc(i)
end;
end; { case }
inc(i);
end;
end;
function ParsePHPCode(const str: String; InPHP: Boolean = False): TPHPDocument;
var
el: TPHPCodeElement;
begin
Result := TPHPDocument.Create('Document', 0, 0, nil);
el := Result;
_ParsePHPCode(str, el, 0, InPHP);
end;
end.