0Pricing
C Academy · Урок

Токенизация входных данных

Преобразуйте текст в токены.

«Токенизация входных данных» — бесплатный урок C Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C Academy содержит 4 уроков всего.

Что такое токенизатор

Интерпретатор начинает с преобразования исходного текста в токены — мельчайшие значимые единицы. Для строки 3 + 4 * 2 токенизатор (или лексер) выдаёт числа и операторы.

На этом этапе удаляются пробельные символы и классифицируется каждая группа символов, поэтому синтаксическому анализатору не приходится работать с исходными байтами.

Тип токена

Мы представляем каждый токен с помощью метки перечисления и полезной нагрузки. Числа содержат целочисленное значение, а операторам и скобкам нужен только их вид.

Хранение значения внутри структуры избавляет от повторного сканирования исходного текста позже.

typedef enum {
  TOK_NUM, TOK_PLUS, TOK_MINUS,
  TOK_STAR, TOK_SLASH,
  TOK_LPAREN, TOK_RPAREN, TOK_EOF
} TokKind;

typedef struct {
  TokKind kind;
  int value; /* used when kind == TOK_NUM */
} Token;

Позиция сканирования

Лексер проходит по исходному тексту с помощью указателя-курсора. Небольшая вспомогательная функция заглядывает на текущий символ, не извлекая его, и возвращает '\0' в конце.

Арифметика указателей делает сканирование быстрым и простым.

static const char *src;

static char peek(void) {
  return *src;
}

static char advance(void) {
  return *src++;
}

Пропуск пробельных символов

Перед чтением токена мы отбрасываем пробелы и табуляции. Стандартная функция isspace из <ctype.h> обрабатывает все пробельные символы.

Здесь переводы строк считаются пробельными символами, поэтому выражение может занимать несколько строк.

#include <ctype.h>

static void skip_ws(void) {
  while (isspace((unsigned char)peek()))
    advance();
}

Лексический разбор числа

Когда курсор указывает на цифру, мы накапливаем последовательные цифры в целое число. Умножение на десять и добавление каждой цифры формирует значение слева направо.

Цикл останавливается на первой нецифре, оставляя курсор готовым к чтению следующего токена.

static int lex_number(void) {
  int n = 0;
  while (isdigit((unsigned char)peek())) {
    n = n * 10 + (advance() - '0');
  }
  return n;
}

Функция next_token

Основная процедура пропускает пробельные символы, а затем выбирает действие по текущему символу. Цифры превращаются в TOK_NUM, а каждому оператору соответствует собственный вид.

Достижение завершающего NUL даёт TOK_EOF — сигнал остановки.

static Token next_token(void) {
  skip_ws();
  char c = peek();
  if (c == '\0') return (Token){TOK_EOF, 0};
  if (isdigit((unsigned char)c))
    return (Token){TOK_NUM, lex_number()};
  advance();
  switch (c) {
    case '+': return (Token){TOK_PLUS, 0};
    case '-': return (Token){TOK_MINUS, 0};
    case '*': return (Token){TOK_STAR, 0};
    case '/': return (Token){TOK_SLASH, 0};
    case '(': return (Token){TOK_LPAREN, 0};
    case ')': return (Token){TOK_RPAREN, 0};
  }
  return (Token){TOK_EOF, 0};
}

Запуск лексера

Вот полная программа, которая разбирает выражение на токены и выводит вид каждого токена. Для чисел также выводится их значение.

Обратите внимание: цикл завершается, когда считывает TOK_EOF.

#include <stdio.h>
#include <ctype.h>

typedef enum { TOK_NUM, TOK_PLUS, TOK_STAR, TOK_EOF } TokKind;
typedef struct { TokKind kind; int value; } Token;

static const char *src;
static char peek(void){ return *src; }
static char advance(void){ return *src++; }

static Token next_token(void){
  while (isspace((unsigned char)peek())) advance();
  char c = peek();
  if (c=='\0') return (Token){TOK_EOF,0};
  if (isdigit((unsigned char)c)){
    int n=0; while(isdigit((unsigned char)peek())) n=n*10+(advance()-'0');
    return (Token){TOK_NUM,n};
  }
  advance();
  if (c=='+') return (Token){TOK_PLUS,0};
  return (Token){TOK_STAR,0};
}

int main(void){
  src = "12 + 3 * 4";
  Token t;
  do {
    t = next_token();
    if (t.kind==TOK_NUM) printf("NUM %d\n", t.value);
    else if (t.kind==TOK_PLUS) printf("PLUS\n");
    else if (t.kind==TOK_STAR) printf("STAR\n");
    else printf("EOF\n");
  } while (t.kind != TOK_EOF);
  return 0;
}

Просмотр одного токена вперёд

Синтаксическим анализаторам обычно нужно проверить следующий токен до его извлечения. Мы храним один токен в глобальной переменной current и обновляем её после каждого совпадения.

Такого просмотра на один токен вперёд достаточно для нашей грамматики LL(1).

static Token current;

static void init_lexer(const char *s) {
  src = s;
  current = next_token();
}

static Token cur(void) { return current; }

static void bump(void) { current = next_token(); }

Сообщение о лексических ошибках

Неизвестный символ — например, $ или @ — не должен молча исчезать. Надёжный лексер сообщает о проблемном байте и прерывает работу.

Немедленное завершение на этапе лексического анализа не позволяет последующим этапам увидеть мусорные токены.

#include <stdio.h>
#include <stdlib.h>

static void lex_error(char c) {
  fprintf(stderr, "lex error: unexpected '%c'\n", c);
  exit(1);
}

Много一символьные операторы

В настоящих языках встречаются токены вроде == или <=. Чтобы разобрать их, мы заглядываем ещё на один символ после первого.

Если следующий байт завершает оператор, мы извлекаем оба символа; иначе выдаём форму из одного символа.

/* fragment: distinguish '=' from '==' */
if (peek() == '=') {
  advance();
  if (peek() == '=') { advance(); /* TOK_EQ */ }
  else { /* TOK_ASSIGN */ }
}

Почему токены важны

Сводя символы к токенам, синтаксический анализатор работает с чистым типизированным потоком. Правила приоритета операторов, группировка и ошибки становятся понятнее.

Далее мы передадим эти токены синтаксическому анализатору с рекурсивным спуском.

Быстрая проверка

Подумайте, какие токены лексер выдаёт для символов группировки.

Итоги

Вы создали лексер: тип токена, курсор сканирования, пропуск пробельных символов, лексический разбор чисел и диспетчер next_token с просмотром одного токена вперёд.

Эти токены являются входными данными для синтаксического анализа, который строит из них синтаксическое дерево.

Часто задаваемые вопросы

Урок «Токенизация входных данных» бесплатный?

Да — полный текст урока «Токенизация входных данных» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C Academy, подпишись на CoddyKit PRO. Курс C Academy содержит 4 уроков всего.

Чему я научусь в уроке «Токенизация входных данных»?

Преобразуйте текст в токены. Ты практикуешь C Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C Academy?

Предыдущий опыт не требуется. C Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Токенизация входных данных»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C Academy?

Да. Каждый урок C Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Токенизация входных данных
  2. Разбор выражений
  3. Вычисление дерева
  4. Добавление переменных
← Назад к C Academy