Создание компилятора языка для .NET Framework - Сканер

ОГЛАВЛЕНИЕ

Сканер

Основной задачей сканера является разбивка текста (потока символов в исходном файле) на куски (именуемые лексемами), которые может потребить анализатор. Сканер определяет, какие лексемы отсылаются анализатору и, следовательно, может выкидывать то, что не определено синтаксисом, скажем комментарии. Что касается моего языка Good for Nothing, сканер интересуют символы (A-Z и прочие обычные), числа (0-9), символы, определяющие операции (такие как +, -, * и /), кавычки для инкапсуляции строк и точки с запятой.

Сканнер собирает в группы потоки связанных символов, формируя лексемы для анализатора. Например, поток символов " h e l l o w o r l d ! " будет сгруппирован в одну лексему: "hello world!".

Сканер Good for Nothing чрезвычайно прост, требуя лишь System.IO.TextReader при создании экземпляра. Это запускает процесс сканирования, как показано ниже:

public Scanner(TextReader input)
{
  this.result = new Collections.List<object>();
  this.Scan(input);
}

рис. 3 иллюстрирует метод Scan («Сканирование»), у которого имеется простой цикл while, проходящий по каждому символу в текстовом потоке и находящий узнаваемые символы, заявленные в определении языка. При каждом обнаружении узнаваемого символа или группы символом, сканер создает лексему и добавляет ее к List<object>. (В дано случае, я ввожу это как объект. Однако я мог бы создать класс Token («Лексема») или что-нибудь подобное для инкапсуляции дополнительной информации о лексеме, такой как строка и номера столбцов.)

Figure 3 The Scanner's Scan Method

private void Scan(TextReader input)
{
  while (input.Peek() != -1)
  {
  char ch = (char)input.Peek();

  // Scan individual tokens
  if (char.IsWhiteSpace(ch))
  {
  // eat the current char and skip ahead
  input.Read();
  }
  else if (char.IsLetter(ch) || ch == '_')
  {
  StringBuilder accum = new StringBuilder();

  input.Read(); // skip the '"'

  if (input.Peek() == -1)
  {
  throw new Exception("unterminated string literal");
  }

  while ((ch = (char)input.Peek()) != '"')
  {
  accum.Append(ch);
  input.Read();

  if (input.Peek() == -1)
  {
  throw new Exception("unterminated string literal");
  }
  }

  // skip the terminating "
  input.Read();
  this.result.Add(accum);
  }
   
  ...
  }
}

Можно заметить, что когда код сталкивается с символом ", он предполагает, что этот символ инкапсулирует лексему строки, следовательно, я поглощаю строку, оборачиваю ее в экземпляр StringBuilder и добавляю к списку. После того, как сканирование создает список лексем, лексемы отправляются к классу анализатора через свойство, именуемое Tokens.