tipos tecnica resueltos ppt pmbok metodo ejemplos ejemplo cola coca aplicado delphi parsing token pascal

tecnica - ¿Cuál es la forma más rápida de analizar una línea en Delphi?



tecnica delphi pmbok (9)

Aquí hay una implementación coja de un lexer muy simple. Esto podría darte una idea.

Tenga en cuenta las limitaciones de este ejemplo: no hay memoria intermedia involucrada, no hay Unicode (esto es un extracto de un proyecto Delphi 7). Probablemente necesites aquellos en una implementación seria.

{ Implements a simpe lexer class. } unit Simplelexer; interface uses Classes, Sysutils, Types, dialogs; type ESimpleLexerFinished = class(Exception) end; TProcTableProc = procedure of object; // A very simple lexer that can handle numbers, words, symbols - no comment handling TSimpleLexer = class(TObject) private FLineNo: Integer; Run: Integer; fOffset: Integer; fRunOffset: Integer; // helper for fOffset fTokenPos: Integer; pSource: PChar; fProcTable: array[#0..#255] of TProcTableProc; fUseSimpleStrings: Boolean; fIgnoreSpaces: Boolean; procedure MakeMethodTables; procedure IdentProc; procedure NewLineProc; procedure NullProc; procedure NumberProc; procedure SpaceProc; procedure SymbolProc; procedure UnknownProc; public constructor Create; destructor Destroy; override; procedure Feed(const S: string); procedure Next; function GetToken: string; function GetLineNo: Integer; function GetOffset: Integer; property IgnoreSpaces: boolean read fIgnoreSpaces write fIgnoreSpaces; property UseSimpleStrings: boolean read fUseSimpleStrings write fUseSimpleStrings; end; implementation { TSimpleLexer } constructor TSimpleLexer.Create; begin makeMethodTables; fUseSimpleStrings := false; fIgnoreSpaces := false; end; destructor TSimpleLexer.Destroy; begin inherited; end; procedure TSimpleLexer.Feed(const S: string); begin Run := 0; FLineNo := 1; FOffset := 1; pSource := PChar(S); end; procedure TSimpleLexer.Next; begin fTokenPos := Run; foffset := Run - frunOffset + 1; fProcTable[pSource[Run]]; end; function TSimpleLexer.GetToken: string; begin SetString(Result, (pSource + fTokenPos), Run - fTokenPos); end; function TSimpleLexer.GetLineNo: Integer; begin Result := FLineNo; end; function TSimpleLexer.GetOffset: Integer; begin Result := foffset; end; procedure TSimpleLexer.MakeMethodTables; var I: Char; begin for I := #0 to #255 do case I of ''@'', ''&'', ''}'', ''{'', '':'', '','', '']'', ''['', ''*'', ''^'', '')'', ''('', '';'', ''/'', ''='', ''-'', ''+'', ''#'', ''>'', ''<'', ''$'', ''.'', ''"'', #39: fProcTable[I] := SymbolProc; #13, #10: fProcTable[I] := NewLineProc; ''A''..''Z'', ''a''..''z'', ''_'': fProcTable[I] := IdentProc; #0: fProcTable[I] := NullProc; ''0''..''9'': fProcTable[I] := NumberProc; #1..#9, #11, #12, #14..#32: fProcTable[I] := SpaceProc; else fProcTable[I] := UnknownProc; end; end; procedure TSimpleLexer.UnknownProc; begin inc(run); end; procedure TSimpleLexer.SymbolProc; begin if fUseSimpleStrings then begin if pSource[run] = ''"'' then begin Inc(run); while pSource[run] <> ''"'' do begin Inc(run); if pSource[run] = #0 then begin NullProc; end; end; end; Inc(run); end else inc(run); end; procedure TSimpleLexer.IdentProc; begin while pSource[Run] in [''_'', ''A''..''Z'', ''a''..''z'', ''0''..''9''] do Inc(run); end; procedure TSimpleLexer.NumberProc; begin while pSource[run] in [''0''..''9''] do inc(run); end; procedure TSimpleLexer.SpaceProc; begin while pSource[run] in [#1..#9, #11, #12, #14..#32] do inc(run); if fIgnoreSpaces then Next; end; procedure TSimpleLexer.NewLineProc; begin inc(FLineNo); inc(run); case pSource[run - 1] of #13: if pSource[run] = #10 then inc(run); end; foffset := 1; fRunOffset := run; end; procedure TSimpleLexer.NullProc; begin raise ESimpleLexerFinished.Create(''''); end; end.

Tengo un archivo enorme que debo analizar línea por línea. La velocidad es esencial.

Ejemplo de una línea:

Token-1 Here-is-the-Next-Token Last-Token-on-Line ^ ^ Current Position Position after GetToken

Se llama a GetToken, devolviendo "Here-is-the-Next-Token" y establece CurrentPosition en la posición del último carácter del token para que esté listo para la próxima llamada a GetToken. Los tokens están separados por uno o más espacios.

Suponga que el archivo ya está en StringList en la memoria. Se adapta fácilmente a la memoria, digamos 200 MB.

Solo me preocupa el tiempo de ejecución para el análisis sintáctico. ¿Qué código producirá la ejecución más rápida absoluta en Delphi (Pascal)?


Creo que el cuello de botella más grande siempre será tener el archivo en la memoria. Una vez que lo tenga en la memoria (obviamente, no todo al mismo tiempo, pero trabajaría con búferes si fuera usted), el análisis real debería ser insignificante.


Esto genera otra pregunta: ¿qué tan grande? Danos una pista como # de líneas o # o Mb (Gb)? Entonces sabremos si cabe en la memoria, necesita estar basado en disco, etc.

En el primer paso usaría mi WordList (S: String; AList: TStringlist);

luego puedes acceder a cada token como Alist [n] ... u ordenarlos o lo que sea.


Hice un analizador léxico basado en un motor de estado (DFA). Funciona con una tabla y es bastante rápido. Pero hay posibles opciones más rápidas.

También depende del idioma. Un lenguaje simple puede tener un algoritmo inteligente.

La tabla es una matriz de registros, cada uno con 2 caracteres y 1 entero. Para cada token, el lexer camina a través de la mesa, comenzando en la posición 0:

state := 0; result := tkNoToken; while (result = tkNoToken) do begin if table[state].c1 > table[state].c2 then result := table[state].value else if (table[state].c1 <= c) and (c <= table[state].c2) then begin c := GetNextChar(); state := table[state].value; end else Inc(state); end;

Es simple y funciona como un encanto.


La forma más rápida de escribir el código probablemente sea crear una TStringList y asignar cada línea en su archivo de texto a la propiedad CommaText. De forma predeterminada, el espacio en blanco es un delimitador, por lo que obtendrá un elemento StringList por token.

MyStringList.CommaText := s; for i := 0 to MyStringList.Count - 1 do begin // process each token here end;

Sin embargo, probablemente obtendrás un mejor rendimiento al analizar cada línea.


La velocidad siempre será relativa a lo que está haciendo una vez que se analiza. Un analizador léxico por lejos es el método más rápido de conversión a tokens desde una secuencia de texto, independientemente del tamaño. TParser en la unidad de clases es un gran lugar para comenzar.

Personalmente ha pasado un tiempo desde que necesité escribir un analizador sintáctico, pero otro método más anticuado pero probado sería usar LEX / YACC para construir una gramática y luego convertirla en un código que pueda usar para realizar su procesamiento. DYacc es una versión de Delphi ... no estoy seguro de si todavía compila o no, pero vale la pena mirar si quieres hacer cosas de la vieja escuela. El libro de dragones aquí sería de gran ayuda, si puedes encontrar una copia.


Rodar uno mismo es la forma más rápida de seguro. Para obtener más información sobre este tema, puede ver el código fuente de Synedit que contiene lexers (llamados resaltadores en el contexto del proyecto) para cualquier idioma en el mercado. Te sugiero que tomes uno de esos lexers como base y lo modifiques para tu propio uso.


  • Use PChar incrementando la velocidad de procesamiento
  • Si no se necesitan algunos tokens, solo copie los datos del token a pedido
  • Copie PChar a la variable local cuando en realidad escanee caracteres
  • Mantenga los datos de origen en un solo búfer a menos que deba manejar línea por línea, e incluso entonces, considere manejar el procesamiento de línea como un token separado en el reconocedor de lexer
  • Considere procesar un búfer de matriz de bytes que viene directamente del archivo, si definitivamente conoce la codificación; si usa Delphi 2009, use PAnsiChar en lugar de PChar, a menos que, por supuesto, sepa que la codificación es UTF16-LE.
  • Si sabe que el único espacio en blanco será # 32 (espacio ASCII) o un conjunto de caracteres similarmente limitado, puede haber algunos ataques de manipulación inteligente de bits que pueden permitirle procesar 4 bytes a la vez usando el barrido de enteros. Sin embargo, no esperaría grandes ganancias aquí, y el código será tan claro como el barro.

Aquí hay un lexer de muestra que debería ser bastante eficiente, pero supone que todos los datos fuente están en una sola cadena. Volver a trabajar para manejar los búferes es moderadamente difícil debido a tokens muy largos.

type TLexer = class private FData: string; FTokenStart: PChar; FCurrPos: PChar; function GetCurrentToken: string; public constructor Create(const AData: string); function GetNextToken: Boolean; property CurrentToken: string read GetCurrentToken; end; { TLexer } constructor TLexer.Create(const AData: string); begin FData := AData; FCurrPos := PChar(FData); end; function TLexer.GetCurrentToken: string; begin SetString(Result, FTokenStart, FCurrPos - FTokenStart); end; function TLexer.GetNextToken: Boolean; var cp: PChar; begin cp := FCurrPos; // copy to local to permit register allocation // skip whitespace; this test could be converted to an unsigned int // subtraction and compare for only a single branch while (cp^ > #0) and (cp^ <= #32) do Inc(cp); // using null terminater for end of file Result := cp^ <> #0; if Result then begin FTokenStart := cp; Inc(cp); while cp^ > #32 do Inc(cp); end; FCurrPos := cp; end;


Si la velocidad es esencial, el código personalizado es la respuesta. Consulte la API de Windows que mapeará su archivo en la memoria. A continuación, puede utilizar un puntero al siguiente personaje para hacer sus tokens, marchando según sea necesario.

Este es mi código para hacer un mapeo:

procedure TMyReader.InitialiseMapping(szFilename : string); var // nError : DWORD; bGood : boolean; begin bGood := False; m_hFile := CreateFile(PChar(szFilename), GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0); if m_hFile <> INVALID_HANDLE_VALUE then begin m_hMap := CreateFileMapping(m_hFile, nil, PAGE_READONLY, 0, 0, nil); if m_hMap <> 0 then begin m_pMemory := MapViewOfFile(m_hMap, FILE_MAP_READ, 0, 0, 0); if m_pMemory <> nil then begin htlArray := Pointer(Integer(m_pMemory) + m_dwDataPosition); bGood := True; end else begin // nError := GetLastError; end; end; end; if not bGood then raise Exception.Create(''Unable to map token file into memory''); end;