-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathScannerModule.java
More file actions
176 lines (158 loc) · 6.59 KB
/
Copy pathScannerModule.java
File metadata and controls
176 lines (158 loc) · 6.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Исключение, возникающее при ошибках лексического анализа.
* Содержит информацию о месте ошибки в исходном коде.
*/
class ScanError extends RuntimeException {
/**
* Конструктор ошибки сканирования.
* @param message описание ошибки
* @param ln номер строки
* @param pos позиция в строке
*/
public ScanError(String message, int ln, int pos) {
super(String.format("[Сканер] строка %d, позиция %d: %s", ln, pos, message));
}
}
/**
* Лексический анализатор (сканер).
* Преобразует исходный код в последовательность токенов.
*/
public class ScannerModule {
private final String code; // исходный код программы
private int idx = 0; // текущая позиция в строке
private int ln = 1; // текущий номер строки
private int pos = 1; // текущая позиция в строке
// Словарь зарезервированных слов языка
private static final Map<String, TokType> RESERVED = new HashMap<>();
static {
RESERVED.put("void", TokType.VOID); // ключевое слово void
RESERVED.put("int", TokType.INT); // ключевое слово int
RESERVED.put("return", TokType.RETURN); // ключевое слово return
}
/**
* Конструктор сканера.
* @param code исходный код программы
*/
public ScannerModule(String code) {
this.code = code;
}
/**
* Возвращает текущий символ без продвижения по строке.
* @return текущий символ или null, если достигнут конец строки
*/
private Character ch() {
if (idx >= code.length()) {
return null;
}
return code.charAt(idx);
}
/**
* Возвращает текущий символ и продвигается к следующему.
* Обновляет счетчики строк и позиций.
* @return текущий символ или null
*/
private Character nextCh() {
Character c = ch();
idx++;
if (c != null) {
if (c == '\n') {
ln++; // новая строка
pos = 1; // сброс позиции
} else {
pos++; // следующая позиция в строке
}
}
return c;
}
/**
* Пропускает все пробельные символы.
*/
private void eatSpaces() {
while (ch() != null && Character.isWhitespace(ch())) {
nextCh();
}
}
/**
* Извлекает идентификатор из входного потока.
* Идентификатор начинается с буквы или '_' и может содержать буквы, цифры и '_'.
* @return токен идентификатора или ключевого слова
*/
private Tok grabIdent() {
int startLn = ln; // запоминаем начальную позицию
int startPos = pos;
StringBuilder buf = new StringBuilder();
// Собираем все символы, составляющие идентификатор
while (ch() != null && (Character.isLetterOrDigit(ch()) || ch() == '_')) {
buf.append(nextCh());
}
String ident = buf.toString();
// Проверяем, является ли идентификатор ключевым словом
TokType kind = RESERVED.getOrDefault(ident, TokType.IDENT);
return new Tok(kind, ident, startLn, startPos);
}
/**
* Извлекает числовую константу из входного потока.
* Число состоит из последовательности цифр.
* @return токен числа
*/
private Tok grabNum() {
int startLn = ln; // запоминаем начальную позицию
int startPos = pos;
StringBuilder buf = new StringBuilder();
// Собираем все цифры
while (ch() != null && Character.isDigit(ch())) {
buf.append(nextCh());
}
// Преобразуем строку в число
return new Tok(TokType.NUMBER, Integer.parseInt(buf.toString()), startLn, startPos);
}
/**
* Основной метод сканирования.
* Преобразует весь исходный код в список токенов.
* @return список токенов
* @throws ScanError при обнаружении недопустимого символа
*/
public List<Tok> scanAll() {
List<Tok> result = new ArrayList<>();
// Обрабатываем весь входной поток
while (ch() != null) {
eatSpaces(); // пропускаем пробелы
if (ch() == null) break;
char c = ch();
int currentLn = ln; // позиция текущего символа
int currentPos = pos;
// Обработка разных типов символов
if (Character.isLetter(c) || c == '_') {
// Идентификатор или ключевое слово
result.add(grabIdent());
} else if (Character.isDigit(c)) {
// Числовая константа
result.add(grabNum());
} else {
// Специальные символы и операторы
nextCh(); // Потребляем символ
Tok token = switch (c) {
case '(' -> new Tok(TokType.LPAR, "(", currentLn, currentPos);
case ')' -> new Tok(TokType.RPAR, ")", currentLn, currentPos);
case '{' -> new Tok(TokType.LBRACE, "{", currentLn, currentPos);
case '}' -> new Tok(TokType.RBRACE, "}", currentLn, currentPos);
case ';' -> new Tok(TokType.SEMI, ";", currentLn, currentPos);
case ',' -> new Tok(TokType.COMMA, ",", currentLn, currentPos);
case '+' -> new Tok(TokType.PLUS, "+", currentLn, currentPos);
case '-' -> new Tok(TokType.MINUS, "-", currentLn, currentPos);
default -> throw new ScanError(
String.format("неопознанный символ '%c'", c), currentLn, currentPos
);
};
result.add(token);
}
}
// Добавляем маркер конца потока
result.add(new Tok(TokType.END, null, ln, pos));
return result;
}
}