你必須構建一個貪婪的掃描儀,吃有效的字符,直到下一個字符不能是新令牌的一部分,那麼你有可能是ungetc(3)
的最後一個字符。
下面是一個掃描器(具有測試它的main()
函數),用於解析整數(有符號或無符號)並正確處理空格(或符號)(我認爲:))隨意使用它或根據需要修改。請注意,數字前面的加號或減號會卡住它(您可以通過插入空格來解除粘連)
如果您需要解析更復雜的事物(如浮點數),那麼我會建議您閱讀和使用lex(1)
/flex(1)
掃描儀發生器。
#include <stdio.h>
#include <ctype.h>
#define MAX_ID 64 /* must be at least two. Routine assumes that */
/* single chars include values 0..255 */
#define TOK_EOF 256
#define TOK_ERR 257
#define TOK_INT 258
union tokenval {
int num; /* in case we return an integer */
/* in case you scan other tokens with specific values,
* e.g. floating point numbers, you can add to this union. */
};
/* lexical scanner with one character of lookahead.
* It recognises:
* integers (regexp: [+-]?[0-9][0-9]*)
* symbols (regexp: .)
* comments (regexp: #.*\n) (these are ignored)
*/
int scan(FILE *in, union tokenval *val)
{
int c;
int result = TOK_ERR;
while ((c = fgetc(in)) != EOF) {
if (isspace(c)) /* skip spaces */
continue;
if (c == '#') { /* comment */
while ((c = fgetc(in)) != EOF && (c != '\n')) continue;
/* c == EOF || c == '\n' */
if (c == EOF) return TOK_EOF;
/* c == '\n' */
continue;
}
if (isalpha(c)) { /* identifier */
char *p = val->id;
size_t l = 1;
*p++ = c; /* add read char */
while (isalnum(c = fgetc(in))) {
if (l < MAX_ID-1) { /* add only if we have space */
*p++ = c; l++;
}
}
*p = '\0'; /* terminate the identifier properly */
/* !isalnum(c) */
ungetc(c, in);
return TOK_ID;
}
/* possible signed number */
switch(c) {
case '+': /* possible signed number */
case '-':
result = c; /* save the read char in result until we know
if we have a trailing number. */
c = fgetc(in);
}
/* result in {TOK_ERR, '+', '-'} */
if (isdigit(c)) { /* integer */
val->num = c - '0';
while (isdigit(c = fgetc(in))) {
val->num *= 10;
val->num += c - '0';
}
/* !isdigit(c) */
ungetc(c, in);
if (result == '-') val->num = -val->num;
return TOK_INT;
}
return result == TOK_ERR ? c : result;
} /* while */
/* c == EOF */
return TOK_EOF;
} /* scan */
int main() {
int tok;
union tokenval val;
#define EOL() puts("")
#define P(tok) printf("[%s-%d]", #tok, (tok))
#define PS(tok) printf("['%c'-%d]\n", (tok), (tok))
do {
tok = scan(stdin, &val);
switch(tok) {
case TOK_ERR: P(TOK_ERR); EOL(); break;
case TOK_INT: P(TOK_INT); printf(":%d\n", val.num); break;
case TOK_EOF: P(TOK_EOF); EOL(); break;
default: PS(tok); break;
} /* switch */
} while (tok != TOK_EOF);
} /* main */
注
問題(和我沒有將它的原因)與示出的掃描儀,沒有處理的e-1
在浮點數後後綴是,這需要能夠在掃描器回溯多個字符(如果您讀取了有效的浮點數 - 例如2.71
---,然後是e-
),您仍然需要閱讀另一個字符以決定是否放大浮點數,以防萬一您得到-
後的數字符號,你必須推回已經讀取的-
和e
char按照該順序)在讀取下一個標記時被包括。我沒有包含能夠讀取浮點數的掃描器,因爲使用lex(1)
/flex(1)
很容易解決這個問題,並且它增加了所示代碼的複雜性。
注2:
如果使用lex(1)
/flex(1)
,對於上面的掃描儀掃描儀規格可以是:
%{
#include <stdio.h>
#include <stdlib.h>
/* single chars include values 0..255 */
#define TOK_EOF 0
#define TOK_ERR 256
#define TOK_INT 257
#define TOK_DBL 258
#define TOK_ID 259
%}
%%
[+-]?[0-9][0-9]* { return TOK_INT; /* integer */ }
[+-]?([0-9]*\.[0-9]+|[0-9]+\.[0-9]*)([eE][+-]?[0-9]+)? { return TOK_DBL; /* double */ }
#.*\n ; /* comment, ignored */
[a-zA-Z][a-zA-Z0-9]* { return TOK_ID; /* ident */ }
[ \t\n] ; /* space, ignored */
. { return yytext[0]; /* the operator char */ }
%%
int main()
{
int tok;
do {
tok = yylex();
switch(tok) {
#define C(ptok) case TOK_##ptok: do{printf("[TOK_"#ptok"-%d]: %s\n", TOK_##ptok, yytext);}while(0)
C(EOF); break;
C(ERR); return 1;
C(INT); break;
C(DBL); break;
C(ID); break;
default: printf("['%c'-%d]\n", tok, tok); break;
}
} while(tok != TOK_EOF);
return 0;
} /* main */
int yywrap() /* so we don't need to link with -ll */
{
return 1;
}
你必須決定你是否永遠只能輸入單個數字,還是可以輸入多位數字。如果你只允許一位數字,那麼你可以讀取數字作爲一個數字(不使用'strtod()')從'56 *'自動獲得'5','6'和'*'。如果你允許多位數字,那麼'56 *'是一個錯誤;堆棧中沒有足夠的數字供'*'操作符使用。你會選擇一個或另一個 - 哪個並不重要。只允許輸入單個數字的數字是相當嚴格的。我建議你堅持讓用戶用空格分隔數字。 –
謝謝@JonathanLeffler。如果我決定將輸入保持爲單個數字以解決無空白問題並同時接受e符號作爲負整數,那麼我該如何更改代碼? –
您必須重做當前調用'strtod()'的代碼。但坦率地說,允許使用單個數字的指數符號是沒有意義的。標誌可以更容易地處理。 –