... | ... | @@ -44,171 +44,250 @@ |
|
|
|
|
|
### Модуль лексического анализатора
|
|
|
|
|
|
``` Delphi
|
|
|
// Распознавание программы на простом языке. Распознаватель лексем (лексер)
|
|
|
{
|
|
|
Лексемы:
|
|
|
; := += -= *= id num begin end cycle
|
|
|
|
|
|
Ключевые слова:
|
|
|
begin end cycle
|
|
|
``` CSharp
|
|
|
using System;
|
|
|
using System.Collections;
|
|
|
using System.Collections.Generic;
|
|
|
using System.IO;
|
|
|
|
|
|
namespace SimpleLangLexer
|
|
|
{
|
|
|
|
|
|
public class LexerException : System.Exception
|
|
|
{
|
|
|
public LexerException(string msg)
|
|
|
: base(msg)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
public enum Tok
|
|
|
{
|
|
|
EOF,
|
|
|
ID,
|
|
|
INUM,
|
|
|
COLON,
|
|
|
SEMICOLON,
|
|
|
ASSIGN,
|
|
|
BEGIN,
|
|
|
END,
|
|
|
CYCLE
|
|
|
}
|
|
|
|
|
|
public class Lexer
|
|
|
{
|
|
|
private int position;
|
|
|
private char currentCh; // Текущий символ
|
|
|
public int LexRow, LexCol; // Строка-столбец начала лексемы. Конец лексемы = LexCol+LexText.Length
|
|
|
private int row, col; // текущие строка и столбец в файле
|
|
|
private TextReader inputReader;
|
|
|
private Dictionary<string, Tok> keywordsMap; // Словарь, сопоставляющий ключевым словам константы типа TLex. Инициализируется процедурой InitKeywords
|
|
|
public Tok LexKind; // Тип лексемы
|
|
|
public string LexText; // Текст лексемы
|
|
|
public int LexValue; // Целое значение, связанное с лексемой LexNum
|
|
|
|
|
|
private string CurrentLineText; // Накапливает символы текущей строки для сообщений об ошибках
|
|
|
|
|
|
|
|
|
public Lexer(TextReader input)
|
|
|
{
|
|
|
CurrentLineText = "";
|
|
|
inputReader = input;
|
|
|
keywordsMap = new Dictionary<string, Tok>();
|
|
|
InitKeywords();
|
|
|
row = 1; col = 0;
|
|
|
NextCh(); // Считать первый символ в ch
|
|
|
NextLexem(); // Считать первую лексему, заполнив LexText, LexKind и, возможно, LexValue
|
|
|
}
|
|
|
|
|
|
public void Init() {
|
|
|
|
|
|
}
|
|
|
|
|
|
private void PassSpaces()
|
|
|
{
|
|
|
while (char.IsWhiteSpace(currentCh))
|
|
|
{
|
|
|
NextCh();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void InitKeywords()
|
|
|
{
|
|
|
keywordsMap["begin"] = Tok.BEGIN;
|
|
|
keywordsMap["end"] = Tok.END;
|
|
|
keywordsMap["cycle"] = Tok.CYCLE;
|
|
|
}
|
|
|
|
|
|
public string FinishCurrentLine()
|
|
|
{
|
|
|
return CurrentLineText + inputReader.ReadLine();
|
|
|
}
|
|
|
|
|
|
private void LexError(string message)
|
|
|
{
|
|
|
System.Text.StringBuilder errorDescription = new System.Text.StringBuilder();
|
|
|
errorDescription.AppendFormat("Lexical error in line {0}:", row);
|
|
|
errorDescription.Append("\n");
|
|
|
errorDescription.Append(FinishCurrentLine());
|
|
|
errorDescription.Append("\n");
|
|
|
errorDescription.Append(new String(' ', col - 1) + '^');
|
|
|
errorDescription.Append('\n');
|
|
|
if (message != "")
|
|
|
{
|
|
|
errorDescription.Append(message);
|
|
|
}
|
|
|
throw new LexerException(errorDescription.ToString());
|
|
|
}
|
|
|
|
|
|
private void NextCh()
|
|
|
{
|
|
|
// В LexText накапливается предыдущий символ и считывается следующий символ
|
|
|
LexText += currentCh;
|
|
|
var nextChar = inputReader.Read();
|
|
|
if (nextChar != -1)
|
|
|
{
|
|
|
currentCh = (char)nextChar;
|
|
|
if (currentCh != '\n')
|
|
|
{
|
|
|
col += 1;
|
|
|
CurrentLineText += currentCh;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
row += 1;
|
|
|
col = 0;
|
|
|
CurrentLineText = "";
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
currentCh = (char)0; // если достигнут конец файла, то возвращается #0
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void NextLexem()
|
|
|
{
|
|
|
PassSpaces();
|
|
|
// R К этому моменту первый символ лексемы считан в ch
|
|
|
LexText = "";
|
|
|
LexRow = row;
|
|
|
LexCol = col;
|
|
|
// Тип лексемы определяется по ее первому символу
|
|
|
// Для каждой лексемы строится синтаксическая диаграмма
|
|
|
if (currentCh == ';')
|
|
|
{
|
|
|
NextCh();
|
|
|
LexKind = Tok.SEMICOLON;
|
|
|
}
|
|
|
else if (currentCh == ':')
|
|
|
{
|
|
|
NextCh();
|
|
|
if (currentCh != '=')
|
|
|
{
|
|
|
LexError("= was expected");
|
|
|
}
|
|
|
NextCh();
|
|
|
LexKind = Tok.ASSIGN;
|
|
|
}
|
|
|
else if (char.IsLetter(currentCh))
|
|
|
{
|
|
|
while (char.IsLetterOrDigit(currentCh))
|
|
|
{
|
|
|
NextCh();
|
|
|
}
|
|
|
if (keywordsMap.ContainsKey(LexText))
|
|
|
{
|
|
|
LexKind = keywordsMap[LexText];
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
LexKind = Tok.ID;
|
|
|
}
|
|
|
}
|
|
|
else if (char.IsDigit(currentCh))
|
|
|
{
|
|
|
while (char.IsDigit(currentCh))
|
|
|
{
|
|
|
NextCh();
|
|
|
}
|
|
|
LexValue = Int32.Parse(LexText);
|
|
|
LexKind = Tok.INUM;
|
|
|
}
|
|
|
else if ((int)currentCh == 0)
|
|
|
{
|
|
|
LexKind = Tok.EOF;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
LexError("Incorrect symbol " + currentCh);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public virtual void ParseToConsole()
|
|
|
{
|
|
|
do
|
|
|
{
|
|
|
Console.WriteLine(TokToString(LexKind));
|
|
|
NextLexem();
|
|
|
} while (LexKind != Tok.EOF);
|
|
|
}
|
|
|
|
|
|
public string TokToString(Tok t)
|
|
|
{
|
|
|
var result = t.ToString();
|
|
|
switch (t)
|
|
|
{
|
|
|
case Tok.ID: result += ' ' + LexText;
|
|
|
break;
|
|
|
case Tok.INUM: result += ' ' + LexValue.ToString();
|
|
|
break;
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
unit SimpleLangLexer;
|
|
|
|
|
|
interface
|
|
|
|
|
|
// TLex - перечислимый тип - все лексемы грамматики
|
|
|
// lexEot - конец текста программы
|
|
|
type
|
|
|
Tok = (EOF, ID, INUM, COLON, SEMICOLON, ASSIGN, &BEGIN, &END, CYCLE);
|
|
|
|
|
|
var
|
|
|
fname: string; // Имя файла программы
|
|
|
LexRow,LexCol: integer; // Строка-столбец начала лексемы. Конец лексемы = LexCol+Length(LexText)
|
|
|
LexKind: Tok; // Тип лексемы
|
|
|
LexText: string; // Текст лексемы
|
|
|
LexValue: integer; // Целое значение, связанное с лексемой lexNum
|
|
|
|
|
|
procedure NextLexem;
|
|
|
procedure Init(fn: string);
|
|
|
procedure Done;
|
|
|
function TokToString(t: Tok): string;
|
|
|
|
|
|
implementation
|
|
|
|
|
|
var
|
|
|
ch: Char; // Текущий символ
|
|
|
f: text; // Текущий файл
|
|
|
row,col: integer; // Текущие строка и столбец в файле
|
|
|
KeywordsMap := new Dictionary<string,Tok>; // Словарь, сопоставляющий ключевым словам константы типа TLex. Инициализируется процедурой InitKeywords
|
|
|
|
|
|
|
|
|
procedure LexError(message: string); // ошибка лексического анализатора
|
|
|
begin
|
|
|
var ss := System.IO.File.ReadLines(fname).Skip(row-1).First(); // Строка row файла
|
|
|
writeln('Лексическая ошибка в строке ',row,':');
|
|
|
writeln(ss);
|
|
|
writeln('^':col-1);
|
|
|
if message<>'' then
|
|
|
writeln(message);
|
|
|
Done;
|
|
|
halt;
|
|
|
end;
|
|
|
|
|
|
procedure NextCh;
|
|
|
begin
|
|
|
// В LexText накапливается предыдущий символ и считывается следующий символ
|
|
|
LexText += ch;
|
|
|
if not f.Eof then
|
|
|
begin
|
|
|
read(f,ch);
|
|
|
if ch<>#10 then
|
|
|
col += 1
|
|
|
else
|
|
|
begin
|
|
|
row += 1;
|
|
|
col := 1;
|
|
|
end;
|
|
|
end
|
|
|
else
|
|
|
begin
|
|
|
ch := #0; // если достигнут конец файла, то возвращается #0
|
|
|
Done;
|
|
|
end;
|
|
|
end;
|
|
|
|
|
|
procedure PassSpaces;
|
|
|
begin
|
|
|
while char.IsWhiteSpace(ch) do
|
|
|
NextCh;
|
|
|
end;
|
|
|
|
|
|
procedure NextLexem;
|
|
|
begin
|
|
|
PassSpaces;
|
|
|
// R К этому моменту первый символ лексемы считан в ch
|
|
|
LexText := '';
|
|
|
LexRow := Row;
|
|
|
LexCol := Col;
|
|
|
// Тип лексемы определяется по ее первому символу
|
|
|
// Для каждой лексемы строится синтаксическая диаграмма
|
|
|
case ch of
|
|
|
';': begin
|
|
|
NextCh;
|
|
|
LexKind := Tok.SEMICOLON;
|
|
|
end;
|
|
|
':': begin
|
|
|
NextCh;
|
|
|
if ch<>'=' then
|
|
|
lexerror('= ожидалось');
|
|
|
NextCh;
|
|
|
LexKind := Tok.ASSIGN;
|
|
|
end;
|
|
|
'a'..'z': begin
|
|
|
while ch in ['a'..'z','0'..'9'] do
|
|
|
NextCh;
|
|
|
if KeywordsMap.ContainsKey(LexText) then
|
|
|
LexKind := KeywordsMap[LexText]
|
|
|
else LexKind := Tok.ID;
|
|
|
end;
|
|
|
'0'..'9': begin
|
|
|
while char.IsDigit(ch) do
|
|
|
NextCh;
|
|
|
LexValue := integer.Parse(LexText);
|
|
|
LexKind := Tok.INUM;
|
|
|
end;
|
|
|
#0: LexKind := Tok.EOF;
|
|
|
else lexerror('Неверный символ '+ch);
|
|
|
end;
|
|
|
end;
|
|
|
|
|
|
procedure InitKeywords;
|
|
|
begin
|
|
|
KeywordsMap['begin'] := Tok.&BEGIN;
|
|
|
KeywordsMap['end'] := Tok.&END;
|
|
|
KeywordsMap['cycle'] := Tok.CYCLE;
|
|
|
end;
|
|
|
|
|
|
procedure Init(fn: string);
|
|
|
begin
|
|
|
InitKeywords;
|
|
|
fname := fn;
|
|
|
AssignFile(f,fname);
|
|
|
reset(f);
|
|
|
row := 1; col := 1;
|
|
|
NextCh; // Считать первый символ в ch
|
|
|
NextLexem; // Считать первую лексему, заполнив LexText, LexKind и, возможно, LexValue
|
|
|
end;
|
|
|
|
|
|
procedure Done;
|
|
|
begin
|
|
|
close(f);
|
|
|
end;
|
|
|
|
|
|
function TokToString(t: Tok): string;
|
|
|
begin
|
|
|
Result := t.ToString;
|
|
|
case t of
|
|
|
Tok.ID: Result += ' ' + LexText;
|
|
|
Tok.INUM: Result += ' ' + LexValue;
|
|
|
end;
|
|
|
end;
|
|
|
|
|
|
end.
|
|
|
|
|
|
```
|
|
|
|
|
|
### Основная программа
|
|
|
|
|
|
``` Pascal
|
|
|
uses SimpleLangLexer;
|
|
|
``` CSharp
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
using System.Text;
|
|
|
using System.IO;
|
|
|
using SimpleLangLexer;
|
|
|
|
|
|
begin
|
|
|
Init('a.txt');
|
|
|
repeat
|
|
|
writeln(TokToString(LexKind));
|
|
|
NextLexem;
|
|
|
until LexKind = Tok.EOF;
|
|
|
end.
|
|
|
namespace SimpleLangLexerTest
|
|
|
{
|
|
|
class Program
|
|
|
{
|
|
|
public static void Main()
|
|
|
{
|
|
|
string fileContents = @"begin
|
|
|
id23 := 24;
|
|
|
cycle ; 2 id258 id29 ;
|
|
|
end";
|
|
|
TextReader inputReader = new StringReader(fileContents);
|
|
|
Lexer l = new Lexer(inputReader);
|
|
|
try
|
|
|
{
|
|
|
do
|
|
|
{
|
|
|
Console.WriteLine(l.TokToString(l.LexKind));
|
|
|
l.NextLexem();
|
|
|
} while (l.LexKind != Tok.EOF);
|
|
|
}
|
|
|
catch (LexerException e)
|
|
|
{
|
|
|
Console.WriteLine("lexer error: " + e.Message);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Основное задание
|
... | ... | |