void scan(Scanner *scanner) {
// Initialise the scanner
int length_of_src = strlen(scanner->source);
scanner->tokens = malloc(sizeof(Token) * length_of_src + 1);
int *line = &scanner->line;
*line = 1;
int *column = &scanner->column;
*column = 0;
char *ch = malloc(sizeof(char) * 1);
for (; *column < length_of_src; (*column)++) {
*ch = scanner->source[*column];
identify_token(scanner, ch);
}
Token eof_token = { END_OF_FILE, "EOF", "EOF", *line };
scanner->tokens[NUM_TOKENS + 1] = eof_token;
}
所以我有这段代码,主要焦点是
char *ch = ...
和 identify_token()
这就是它的样子。
void identify_token(Scanner *scanner, char *ch) {
TokenType token_type;
// NOTE: When updating the switch case
// update TokenName token_names[] as well.
switch (*ch) {
case '(':
token_type = LEFT_PAREN;
break;
// other cases...
default:
// TODO: Implement error handling.
printf("Unexpected token.\n");
return;
}
Token token = { token_type, ch, ch, scanner->line };
scanner->tokens[NUM_TOKENS] = token;
NUM_TOKENS++;
}
至于
scanner->source
是这样制作的:
char *source = "((,)).*"; // 7 chars + 1 null byte
Scanner *scanner = malloc(sizeof(Scanner));
// Creates memory for source
scanner->source = malloc(strlen(source));
strcpy(scanner->source, source);
这就是
Token
。
typedef struct Token {
TokenType type;
// The character that represents the TokenType
char *lexeme;
// The value to be evaluated.
// To convert with *(type *)token.literal
void *literal;
int line;
} Token;
在
scan
内部的for循环中,我在每次迭代中将ch
传递给identify_token
。然而,ch
内存位置中的值不断更新,之前的值被覆盖并丢失。我想要的是
因此,使用当前代码,结果是无论 for 循环中的当前字符是什么,
lexeme
中的 Token
都将是该字符。因此,当循环完成时,lexeme
将是scanner->source
中的最后一个字符,在本例中为*
。
所以,当我检查
gdb
中的值时,这就是我得到的。
有人建议我问题的解决方案是动态分配,从搜索中我可以找到行
char *ch = malloc(sizeof(char) * 1);
算作动态分配。所以,我错过了一些东西,但不确定是什么。
我不太确定我能做什么,我唯一尝试过的就是而不是这个
Token token = { token_type, ch, ch, scanner->line };
我有
Token token = { token_type, scanner->source[scanner->column], ch, scanner->line };
只是为了测试一下它是否有效。然而,有两个问题。
scanner->source[scanner->column]
不正确,因为 Token
需要一个指针。为了解决这个问题,我在前面添加了一个 &
。token.lexeme
将是 scanner->source
。我不确定这是否相关,但我觉得有必要说,将来我计划包括比一个字符更长的
lexeme
。例如“var”、“return”等
所以主要问题是我能做些什么来解决这个问题?
从源代码片段来看,
lexeme
结构的Token
字段应该指向词素开始处的源字符串,而literal
字段应该指向一个分配的对象,其类型取决于找到的令牌类型。
identify_token
应该接收一个指向源字符串的指针,并且该指针可以存储到新令牌的 lexeme
字段,并且该函数应该有一种方法告诉其调用者令牌在源中使用了多少字节字符串,以便扫描器可以前进到下一个标记。例如 (
是单字节标记 LEFT_PAREN
,a
可以是单字节标记 CHAR
,其 literal
中的值应该是指向 1 字节字符串 "a"
的指针,并且 \(
是2 字节令牌 CHAR
,其 literal
指向 1 字节字符串 "("
。
这是一个修改版本,您可以学习:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef enum TokenType {
END_OF_FILE, ERROR, LEFT_PAREN, RIGHT_PAREN, COMMA, DOT, STAR, QUESTION, NEW_LINE, CHAR, STRING,
} TokenType;
typedef struct Token {
TokenType type;
// The character that represents the TokenType
const char *lexeme;
// The value to be evaluated.
// To convert with *(type *)token.literal
void *literal;
int line;
} Token;
typedef struct Scanner {
char *source;
int line, column;
int num_tokens;
struct Token *tokens;
} Scanner;
int identify_token(Scanner *scanner, char *ch) {
Token token = { ERROR, ch, NULL, scanner->line };
int len = 1;
switch (*ch) {
case '(':
token.type = LEFT_PAREN;
break;
case ')':
token.type = RIGHT_PAREN;
break;
case ',':
token.type = COMMA;
break;
case '.':
token.type = DOT;
break;
case '*':
token.type = STAR;
break;
case '?':
token.type = QUESTION;
break;
case '\\': // escape special character
token.type = CHAR;
token.literal = strndup(ch + 1, 1);
len = 2;
break;
case '\n':
token.type = NEW_LINE;
scanner->line++;
scanner->column = 0;
break;
// other cases...
default:
len = strcspn(ch, "(),.*?\\\n");
if (len > 1) {
token.type = STRING;
token.literal = strndup(ch, len);
} else {
token.type = CHAR;
token.literal = strndup(ch, 1);
}
break;
}
scanner->tokens[scanner->num_tokens++] = token;
scanner->column += len;
return len;
}
void scan(Scanner *scanner) {
// Initialise the scanner
int length_of_src = strlen(scanner->source);
scanner->tokens = malloc(sizeof(Token) * length_of_src + 1);
scanner->num_tokens = 0;
scanner->line = 1;
scanner->column = 1;
for (int pos = 0; pos < length_of_src;) {
pos += identify_token(scanner, scanner->source + pos);
}
Token eof_token = { END_OF_FILE, scanner->source + length_of_src, strdup("EOF"), scanner->line };
scanner->tokens[scanner->num_tokens++] = eof_token;
}
void print_token(Token token) {
switch (token.type) {
case END_OF_FILE:
printf("<END>");
break;
case LEFT_PAREN:
printf("(");
break;
case RIGHT_PAREN:
printf(")");
break;
case COMMA:
printf(",");
break;
case DOT:
printf(".");
break;
case STAR:
printf("*");
break;
case QUESTION:
printf("?");
break;
case CHAR:
printf("'%c'", *(char *)token.literal);
break;
case STRING:
printf("\"%s\"", (char *)token.literal);
break;
case NEW_LINE:
printf("'\n'");
break;
case ERROR:
printf("ERROR");
break;
}
}
int main(int argc, char *argv[]) {
const char *source = argc > 1 ? argv[1] : "((,)).*";
Scanner *scanner = calloc(sizeof(Scanner), 1);
scanner->source = strdup(source);
scan(scanner);
for (int i = 0; i < scanner->num_tokens; i++) {
print_token(scanner->tokens[i]);
}
printf("\n");
return 0;
}