0Pricing
C Academy · Lekcja

Tokenizacja danych wejściowych

Przekształci Pan/Pani tekst w tokeny.

Tokenizacja danych wejściowych to bezpłatna lekcja C Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C Academy zawiera 4 lekcji w sumie.

Czym jest tokenizer?

Interpreter rozpoczyna działanie od przekształcenia surowego tekstu w tokeny — najmniejsze znaczące jednostki. Dla ciągu 3 + 4 * 2 tokenizer (lub lexer) generuje liczby i operatory.

Ten etap usuwa białe znaki i klasyfikuje każdą grupę znaków, dzięki czemu parser nigdy nie pracuje na surowych bajtach.

Typ tokena

Modelujemy każdy token za pomocą znacznika typu enum i danych. Liczby przechowują wartość całkowitą, a operatory i nawiasy potrzebują jedynie informacji o swoim rodzaju.

Przechowywanie wartości wewnątrz struktury eliminuje konieczność ponownego skanowania źródła.

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;

Pozycja skanowania

Lexer przechodzi po źródle za pomocą wskaźnika pozycji. Mała funkcja pomocnicza podgląda bieżący znak bez pobierania go i zwraca '\0' na końcu.

Arytmetyka wskaźników pozwala zachować szybkość i prostotę skanera.

static const char *src;

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

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

Pomijanie białych znaków

Przed odczytaniem tokena odrzucamy spacje i tabulatory. Standardowa funkcja isspace z <ctype.h> obsługuje każdy znak odstępu.

Znaki nowego wiersza są tutaj traktowane jako białe znaki, więc wyrażenie może zajmować wiele wierszy.

#include <ctype.h>

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

Analizowanie liczby

Gdy wskaźnik pozycji wskazuje cyfrę, gromadzimy kolejne cyfry w liczbie całkowitej. Mnożenie przez dziesięć i dodawanie każdej cyfry buduje wartość od lewej do prawej.

Pętla kończy się na pierwszym znaku niebędącym cyfrą, pozostawiając wskaźnik gotowy do odczytania następnego tokena.

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

Funkcja next_token

Główna procedura pomija białe znaki, a następnie wybiera działanie na podstawie bieżącego znaku. Cyfry stają się tokenem TOK_NUM, a każdy operator otrzymuje własny rodzaj.

Dotarcie do kończącego ciąg NUL-a zwraca TOK_EOF — sygnał nakazujący zakończenie.

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};
}

Uruchamianie lexera

Oto kompletny program, który tokenizuje wyrażenie i wyświetla rodzaj każdego tokena. Dla liczb wyświetla również ich wartość.

Zauważ, że pętla kończy się po odczytaniu 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;
}

Jednotokenowe wyprzedzenie

Parsery zwykle muszą sprawdzić nadchodzący token przed jego pobraniem. Przechowujemy jeden token w globalnej zmiennej current i uzupełniamy ją po każdym dopasowaniu.

To jednotokenowe wyprzedzenie wystarcza dla naszej gramatyki 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(); }

Zgłaszanie błędów leksykalnych

Nieznany znak — na przykład $ lub @ — nie powinien po cichu zniknąć. Solidny lexer zgłasza błędny bajt i przerywa działanie.

Natychmiastowe zakończenie na etapie lexera uniemożliwia późniejszym etapom otrzymanie błędnych tokenów.

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

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

Operatory wieloznakowe

Rzeczywiste języki mają tokeny takie jak == lub <=. Aby je rozpoznać, podglądamy dodatkowy znak po pierwszym.

Jeśli następny bajt dopełnia operator, pobieramy oba znaki; w przeciwnym razie generujemy formę jednoznakową.

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

Dlaczego tokeny są ważne

Po przekształceniu znaków w tokeny parser pracuje na uporządkowanym, typowanym strumieniu. Priorytety operatorów, grupowanie i błędy stają się łatwiejsze do zrozumienia.

Następnie przekażemy te tokeny do parsera rekurencyjnego opadania.

Szybkie sprawdzenie

Zastanów się, co lexer generuje dla symboli grupowania.

Podsumowanie

Zbudowałeś lexer: typ tokena, wskaźnik skanowania, pomijanie białych znaków, analizowanie liczb oraz funkcję next_token z jednotokenowym wyprzedzeniem.

Te tokeny są danymi wejściowymi parsera, który buduje z nich drzewo składniowe.

Często zadawane pytania

Czy lekcja „Tokenizacja danych wejściowych” jest bezpłatna?

Tak — pełny tekst „Tokenizacja danych wejściowych” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C Academy, przejdź na CoddyKit PRO. Kurs C Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Tokenizacja danych wejściowych”?

Przekształci Pan/Pani tekst w tokeny. Ćwiczysz C Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć C Academy?

Nie wymagamy żadnego doświadczenia. C Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „Tokenizacja danych wejściowych”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji C Academy?

Tak. Każda lekcja C Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Tokenizacja danych wejściowych
  2. Parsowanie wyrażeń
  3. Ewaluacja drzewa
  4. Dodawanie zmiennych
← Powrót do C Academy