字符串文字中换行符的换行计数器 [更新]

New line counter for new line characters in string literals [Updated]

注意: 这是我之前的同一个问题,但我将其简化为所需的结构。如果仍然不好看,请告诉我格式有什么问题,以供日后参考。

我有一个 class 的项目,我必须为 TIP 制作一个词法分析器。我在计算屏幕左侧显示的换行符时出错。目前,它有一个正则表达式,每当使用 \n 或 \r 时,它都会递增 line_counter 变量。我的问题是它不接受字符串中的新行符号。该程序将显示它应该如何使用换行符,但是它不会增加计数器。

期望的输出:

到目前为止我做了什么:

错误: 我没有收到任何错误。

以下是我在这个问题中引用的相关正则表达式的片段。 另外,问题的底部是实际和预期的输出。

来自规则文件的片段

 /* STRING LITERAL REGEX */
[']([^'\]|\(.|\n))*[']       { if(yyleng <= 80)
                                    {
                                        return TOK_STRINGLIT; 
                                    }
                                    else
                                    {
                                        return TOK_UNKNOWN;
                                    }
                                }

 /* REGEX TO COUNT NEW LINES */
[\r\n]         { line_number++; }

预期输出

line: 16, lexeme: |'This string
has
newlines
inside of it'|, length: 43, token: 4003
line: 20, lexeme: |&|, length: 1, token: 6000
ERROR: unknown token                            

实际产量

line: 16, lexeme: |'This string
has
newlines 
inside of it'|, length: 43, token: 4003
line: 17, lexeme: |&|, length: 1, token: 6000 
ERROR: unknown token                          

可复制文件

LEXER.H

//*****************************************************************************
// CSE 4713 / 6713 Project - List of tokens for TIPS
//*****************************************************************************

#ifndef LEXER_H
#define LEXER_H

// List of token codes

// Keywords
#define TOK_BEGIN    1000
#define TOK_BREAK    1001
#define TOK_CONTINUE 1002
#define TOK_DOWNTO   1003
#define TOK_ELSE     1004
#define TOK_END      1005
#define TOK_FOR      1006
#define TOK_IF       1007
#define TOK_LET      1008
#define TOK_PROGRAM  1009
#define TOK_READ     1010
#define TOK_THEN     1012
#define TOK_TO       1013
#define TOK_VAR      1014
#define TOK_WHILE    1015
#define TOK_WRITE    1016

// Datatype Specifiers
#define TOK_INTEGER  1100
#define TOK_REAL     1101

// Punctuation
#define TOK_SEMICOLON  2000
#define TOK_COLON      2001
#define TOK_OPENPAREN  2002
#define TOK_CLOSEPAREN 2003
#define TOK_OPENBRACE  2004
#define TOK_CLOSEBRACE 2005

// Operators
#define TOK_PLUS        3000
#define TOK_MINUS       3001
#define TOK_MULTIPLY    3002
#define TOK_DIVIDE      3003
#define TOK_ASSIGN      3004
#define TOK_EQUALTO     3005
#define TOK_LESSTHAN    3006
#define TOK_GREATERTHAN 3007
#define TOK_NOTEQUALTO  3008
#define TOK_MOD         3009
#define TOK_NOT         3010
#define TOK_OR          3011
#define TOK_AND         3012

// Useful abstractions
#define TOK_IDENT       4000  // identifier
#define TOK_INTLIT      4001  // integer literal
#define TOK_FLOATLIT    4002  // floating point literal
#define TOK_STRINGLIT   4003  // string literal
#define TOK_EOF         5000  // end of file
#define TOK_EOF_SL      5001  // end of file while parsing a string literal
#define TOK_UNKNOWN     6000  // unknown lexeme

#endif

DRIVER.CPP

//*****************************************************************************
// CSE 4713 / 6713 Project Part 1 - Lexical Analyzer Driver
// Fall 2020
//*****************************************************************************

#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdio.h>
#include "lexer.h"


// Instantiate global variables
extern "C"
{
extern FILE *yyin;         // input stream
extern FILE *yyout;        // output stream
extern int   yyleng;       // length of current lexeme
extern char *yytext;       // text of current lexeme
extern int   yylex();      // the generated lexical analyzer

extern int   line_number;  // current line number of the input
}

// Do the analysis
int main( int argc, char* argv[] ) {
  int token;   // hold each token code

  // Set the input stream
  if (argc > 1) {
    printf("INFO: Using the file %s for input\n", argv[1]);
    yyin = fopen(argv[1], "r");
    if (!yyin) {
      printf("   ERROR: input file not found\n");
      return (-1);
    }
  }
  else {
    printf("INFO: Using stdin for input, use EOF to end input\n");
    printf("      Windows EOF is Ctrl+z, Linux EOF is Ctrl+d\n");
    yyin = stdin;
  }

  // Set the output stream
  yyout = stdout;
  
  // Do the lexical parsing
  token = yylex();
  while( token != TOK_EOF ) 
  {
    // What did we find?
    fprintf(yyout, "line: %d, lexeme: |%s|, length: %d, token: %d\n", 
                        line_number, yytext, yyleng, token);
    
    // Is it an error?
    if( token == TOK_UNKNOWN )
      fprintf(yyout,"   ERROR: unknown token\n");
    if( token == TOK_EOF_SL )
      fprintf(yyout,"   ERROR: end of file while in a string literal\n");
    
    // Get the next token
    token = yylex();
  }
  return 0;
}

RULES.L

/******************************************************************* 
Starting point your rules.l file for TIPS
Name: Stephanie Schisler                NetID: sas880
Course: CSE 4713                        Assignment: Part 1
Programming Environment: WSL C++
Purpose of File: Contains the rules for the project.
*******************************************************************/
%option noyywrap
%{
#include "lexer.h"

// global variable to hold current line number being read
int line_number = 1;

%}

%%

 /* Keywords */ 
BEGIN           { return TOK_BEGIN; }
BREAK           { return TOK_BREAK; }
CONTINUE        { return TOK_CONTINUE; }
DOWNTO          { return TOK_DOWNTO; }
ELSE            { return TOK_ELSE; }
END             { return TOK_END; }
FOR             { return TOK_FOR; }
IF              { return TOK_IF; }
LET             { return TOK_LET; }
PROGRAM         { return TOK_PROGRAM; }
READ            { return TOK_READ; }
THEN            { return TOK_THEN; }
TO              { return TOK_TO; }
VAR             { return TOK_VAR; }
WHILE           { return TOK_WHILE; }
WRITE           { return TOK_WRITE; }

 /* Datatype Specifiers */
INTEGER         { return TOK_INTEGER; }
REAL            { return TOK_REAL; }

 /* Punctuation */
\;           { return TOK_SEMICOLON; }
\:           { return TOK_COLON; }
\(          { return TOK_OPENPAREN; }
\)          { return TOK_CLOSEPAREN; }
\{          { return TOK_OPENBRACE; }
\}          { return TOK_CLOSEBRACE; }

 /* Operators */
\+          { return TOK_PLUS; }
-           { return TOK_MINUS; }
\*          { return TOK_MULTIPLY; }
\/          { return TOK_DIVIDE; }
\:=          { return TOK_ASSIGN; }
\=           { return TOK_EQUALTO; }
\<           { return TOK_LESSTHAN; }
\>           { return TOK_GREATERTHAN; }
\<>          { return TOK_NOTEQUALTO; }
MOD         { return TOK_MOD; }
NOT         { return TOK_NOT; }
OR          { return TOK_OR; }
AND         { return TOK_AND; }

 /* Abstractions */
[A-Z][0-9A-Z]{0,7}           { return TOK_IDENT; }       
[0-9]+                       { return TOK_INTLIT; }      
[0-9]+[.]?[0-9]+             { return TOK_FLOATLIT; }


[']([^'\]|\(.|\n))*[']       { if(yyleng <= 80)
                                    {
                                        return TOK_STRINGLIT; 
                                    }
                                    else
                                    {
                                        return TOK_UNKNOWN;
                                    }
                                }

"\[[^"\]|\(.|\n)]*|'\[[^'\]|\(.|\n)]*            { return TOK_EOF_SL; }

 /* Count new lines */
[\r\n]         { line_number++; }

 /* Eat any whitespace */
[\t ]*

 /* Found an unknown character */

.         { return TOK_UNKNOWN; }

 /* Recognize end of file */

<<EOF>>   { return TOK_EOF; }


输入文件


ABCDEFGH
ABCDEFGHIJ
AB123 123AB A123ZZ  SUM  IFFINESS
AB_123
ab_123
123 
3219012894910
12.132 
.123
0.132
-123 -12.324
%%%%%%%
^
'This is a string'
'This string    has tabs        inside of it.'
'This string
 has 
 newlines
 inside of it'
&

正确的输出


ABCDEFGH
ABCDEFGHIJ
AB123 123AB A123ZZ  SUM  IFFINESS
AB_123
ab_123
123 
3219012894910
12.132 
.123
0.132
-123 -12.324
%%%%%%%
^
'This is a string'
'This string    has tabs        inside of it.'
'This string
 has 
 newlines
 inside of it'
&

生成文件

###############################################################################
# CSE 4713 / 6713 Project Part 1 - Lexical Analyzer (flex)
#
# 'make'        build executable file
# 'make clean'  removes all intermediate (lex.yy.c and *.o) and executable files
#
# This makefile purposely avoids macros to make the rules more clear.
# For more information about makefiles:
#      http://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/
#      http://www.cs.swarthmore.edu/~newhall/unixhelp/howto_makefiles.html
#      http://www.gnu.org/software/make/manual/make.html
#
###############################################################################

lex.exe: lex.yy.o driver.o
    g++ -g -o lex.exe lex.yy.o driver.o

driver.o: driver.cpp lexer.h
    g++ -g -o driver.o -c driver.cpp

lex.yy.o: lex.yy.c lexer.h
    gcc -g -o lex.yy.o -c lex.yy.c

lex.yy.c: rules.l lexer.h
    flex -o lex.yy.c rules.l

clean: 
    $(RM) *.o lex.yy.c lex.exe


你的行计数器没有被字符串中的换行符增加,因为你对字符串模式的操作没有改变行计数器。

(F)lex 词法分析器根据您提供的模式将输入划分为标记,并针对每个标记执行关联的操作。模式在其他模式中不匹配:这会导致混乱。 (例如,tiffany 不包含 if 标记。它是一个不可分割的标识符。)

从使用 flex 构建的词法分析器中获取准确行数的最简单方法是包含选项

 %option yylineno

在您的序言中(第一个 %% 之前的 flex 输入文件部分)。一旦你这样做了,flex 就会为你做所有的事情,并且 yylineno 将始终包含行号计数。 (它包含标记的 end 处的行号计数,这很重要:如果您想知道多行标记从哪一行开始,您需要执行(非常少)多一点。)

可能有人告诉您不要使用 yylineno。 (就个人而言,我认为这样的作业限制是错误的,但我并不总是看到 eye-to-eye 与讲师。)如果是这种情况,你需要做 flex 会自动为你做的事情,这重新扫描任何可能包含换行符的标记以计算它包含的换行符的数量,如果有的话:

[']([^'\]|\(.|\n))*[']       { for (const char* p = yytext; *p; ++p) {
                                   if (*p == '\n') ++line_number;
                                 }
                                 /* Rest of the string action */ 
                                 ...

您可以通过使用开始条件来处理字符串字面量。例如,您可以将该代码更改为:

%x STRING_LITERAL
%%
[']                  { yymore(); BEGIN(STRING_LITERAL); }
<STRING_LITERAL>{
  [^'\\n]+          { yymore(); }
  \?\r?\n           { ++line_number; yymore(); }
  \.                { yymore(); }
  [']                { BEGIN(INITIAL); /* Return to the normal scan */
                       /* At this point, yytext and yyleng refer to the
                        * entire token, including the ' marks. So you can
                        * now do exactly what you did in your string literal
                        * action. But see below for some comments.
                        */
                        ...
                      }
}
   /* Other lexical rules continue here */

查看 flex manual section on start conditions 以获取有关启动条件如何工作的详细说明。

请注意,除了最后一个实际接受令牌的操作外,字符串令牌中的所有操作都包含对 yymore() 的调用。 yymore() 是一个特殊的 (f)lex 动作,它告诉分析器令牌尚未完成,下一个模式将匹配当前令牌的另一部分。

关于您的代码的一些旁注:

  1. 您的 end-of-line 模式 [\r\n] 匹配 \r\n。 Windows 行结尾实际上是 two-character 序列 \r\n,所以如果遇到一个,你最终会递增 line_number 两次。 (但是,您不太可能遇到这种情况,除非您以二进制模式打开输入文件,因为标准库在读取该行时应该删除 \r。)匹配 [= 的正确模式81=] 或 Unix line-ending 序列是

     \r?\n
    

    这将匹配整个 line-ending。有时您会看到更精细的图案,如果您的讲师关心“永不再见的历史文物博物馆”,他们可能会要求您处理本世纪未曾使用过的约定,例如 pre-OS-X Apple 约定只使用 \r。我的建议是不要接受它。只需计算 \n s,无论它们前面是否有 \r。这在您可能遇到的任何系统上都是正确的。

  2. 您对过长字符串的测试不精确(我想我们已经在另一个问题中提到了这个事实)。首先,它计算字符串文字长度中的引号,这可能是不正确的。其次,它根据转义序列本身的长度来计算转义序列,而不是转义序列被翻译成的单个字符。这将导致将 a 计为单个字符,但在语义上等同的 \x61 计为四个字符,这很可能导致完全有效的 less-than-80-character-long 文字被你的词法扫描器拒绝。如果您使用 start-condition 解决方案来计算上面建议的行数,您还可以通过保持单独的多余字符计数来更正文字的计算长度。 (您不能更改令牌内部的 yyleng,因为 yymore() 取决于开始能够维护它和 yytext。请参阅上面的手册 link。)